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 chrono::TimeZone;
14use deltachat_contact_tools::{ContactAddress, sanitize_bidi_characters, sanitize_single_line};
15use deltachat_derive::{FromSql, ToSql};
16use mail_builder::mime::MimePart;
17use serde::{Deserialize, Serialize};
18use strum_macros::EnumIter;
19
20use crate::blob::BlobObject;
21use crate::chatlist::Chatlist;
22use crate::color::str_to_color;
23use crate::config::Config;
24use crate::constants::{
25 Blocked, Chattype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK, DC_CHAT_ID_LAST_SPECIAL,
26 DC_CHAT_ID_TRASH, DC_RESEND_USER_AVATAR_DAYS, EDITED_PREFIX, TIMESTAMP_SENT_TOLERANCE,
27};
28use crate::contact::{self, Contact, ContactId, Origin};
29use crate::context::Context;
30use crate::debug_logging::maybe_set_logging_xdc;
31use crate::download::DownloadState;
32use crate::ephemeral::{Timer as EphemeralTimer, start_chat_ephemeral_timers};
33use crate::events::EventType;
34use crate::location;
35use crate::log::{LogExt, error, info, warn};
36use crate::logged_debug_assert;
37use crate::message::{self, Message, MessageState, MsgId, Viewtype};
38use crate::mimefactory::MimeFactory;
39use crate::mimeparser::SystemMessage;
40use crate::param::{Param, Params};
41use crate::receive_imf::ReceivedMsg;
42use crate::smtp::send_msg_to_smtp;
43use crate::stock_str;
44use crate::sync::{self, Sync::*, SyncData};
45use crate::tools::{
46 IsNoneOrEmpty, SystemTime, buf_compress, create_id, create_outgoing_rfc724_mid,
47 create_smeared_timestamp, create_smeared_timestamps, get_abs_path, gm2local_offset,
48 smeared_time, time, truncate_msg_text,
49};
50use crate::webxdc::StatusUpdateSerial;
51use crate::{chatlist_events, imap};
52
53#[derive(Debug, Copy, Clone, PartialEq, Eq)]
55pub enum ChatItem {
56 Message {
58 msg_id: MsgId,
60 },
61
62 DayMarker {
65 timestamp: i64,
67 },
68}
69
70#[derive(
72 Debug,
73 Default,
74 Display,
75 Clone,
76 Copy,
77 PartialEq,
78 Eq,
79 FromPrimitive,
80 ToPrimitive,
81 FromSql,
82 ToSql,
83 IntoStaticStr,
84 Serialize,
85 Deserialize,
86)]
87#[repr(u32)]
88pub enum ProtectionStatus {
89 #[default]
91 Unprotected = 0,
92
93 Protected = 1,
97 }
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub(crate) enum CantSendReason {
110 SpecialChat,
112
113 DeviceChat,
115
116 ContactRequest,
118
119 ReadOnlyMailingList,
121
122 InBroadcast,
124
125 NotAMember,
127
128 MissingKey,
130}
131
132impl fmt::Display for CantSendReason {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 match self {
135 Self::SpecialChat => write!(f, "the chat is a special chat"),
136 Self::DeviceChat => write!(f, "the chat is a device chat"),
137 Self::ContactRequest => write!(
138 f,
139 "contact request chat should be accepted before sending messages"
140 ),
141 Self::ReadOnlyMailingList => {
142 write!(f, "mailing list does not have a know post address")
143 }
144 Self::InBroadcast => {
145 write!(f, "Broadcast channel is read-only")
146 }
147 Self::NotAMember => write!(f, "not a member of the chat"),
148 Self::MissingKey => write!(f, "key is missing"),
149 }
150 }
151}
152
153#[derive(
158 Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord,
159)]
160pub struct ChatId(u32);
161
162impl ChatId {
163 pub const fn new(id: u32) -> ChatId {
165 ChatId(id)
166 }
167
168 pub fn is_unset(self) -> bool {
172 self.0 == 0
173 }
174
175 pub fn is_special(self) -> bool {
179 (0..=DC_CHAT_ID_LAST_SPECIAL.0).contains(&self.0)
180 }
181
182 pub fn is_trash(self) -> bool {
189 self == DC_CHAT_ID_TRASH
190 }
191
192 pub fn is_archived_link(self) -> bool {
199 self == DC_CHAT_ID_ARCHIVED_LINK
200 }
201
202 pub fn is_alldone_hint(self) -> bool {
211 self == DC_CHAT_ID_ALLDONE_HINT
212 }
213
214 pub(crate) fn lookup_by_message(msg: &Message) -> Option<Self> {
216 if msg.chat_id == DC_CHAT_ID_TRASH {
217 return None;
218 }
219 if msg.download_state == DownloadState::Undecipherable {
220 return None;
221 }
222 Some(msg.chat_id)
223 }
224
225 pub async fn lookup_by_contact(
230 context: &Context,
231 contact_id: ContactId,
232 ) -> Result<Option<Self>> {
233 let Some(chat_id_blocked) = ChatIdBlocked::lookup_by_contact(context, contact_id).await?
234 else {
235 return Ok(None);
236 };
237
238 let chat_id = match chat_id_blocked.blocked {
239 Blocked::Not | Blocked::Request => Some(chat_id_blocked.id),
240 Blocked::Yes => None,
241 };
242 Ok(chat_id)
243 }
244
245 pub(crate) async fn get_for_contact(context: &Context, contact_id: ContactId) -> Result<Self> {
253 ChatIdBlocked::get_for_contact(context, contact_id, Blocked::Not)
254 .await
255 .map(|chat| chat.id)
256 }
257
258 pub async fn create_for_contact(context: &Context, contact_id: ContactId) -> Result<Self> {
263 ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Not).await
264 }
265
266 pub(crate) async fn create_for_contact_with_blocked(
270 context: &Context,
271 contact_id: ContactId,
272 create_blocked: Blocked,
273 ) -> Result<Self> {
274 let chat_id = match ChatIdBlocked::lookup_by_contact(context, contact_id).await? {
275 Some(chat) => {
276 if create_blocked != Blocked::Not || chat.blocked == Blocked::Not {
277 return Ok(chat.id);
278 }
279 chat.id.set_blocked(context, Blocked::Not).await?;
280 chat.id
281 }
282 None => {
283 if Contact::real_exists_by_id(context, contact_id).await?
284 || contact_id == ContactId::SELF
285 {
286 let chat_id =
287 ChatIdBlocked::get_for_contact(context, contact_id, create_blocked)
288 .await
289 .map(|chat| chat.id)?;
290 ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat).await?;
291 chat_id
292 } else {
293 warn!(
294 context,
295 "Cannot create chat, contact {contact_id} does not exist."
296 );
297 bail!("Can not create chat for non-existing contact");
298 }
299 }
300 };
301 context.emit_msgs_changed_without_ids();
302 chatlist_events::emit_chatlist_changed(context);
303 chatlist_events::emit_chatlist_item_changed(context, chat_id);
304 Ok(chat_id)
305 }
306
307 #[expect(clippy::too_many_arguments)]
310 pub(crate) async fn create_multiuser_record(
311 context: &Context,
312 chattype: Chattype,
313 grpid: &str,
314 grpname: &str,
315 create_blocked: Blocked,
316 create_protected: ProtectionStatus,
317 param: Option<String>,
318 timestamp: i64,
319 ) -> Result<Self> {
320 let grpname = sanitize_single_line(grpname);
321 let timestamp = cmp::min(timestamp, smeared_time(context));
322 let row_id =
323 context.sql.insert(
324 "INSERT INTO chats (type, name, grpid, blocked, created_timestamp, protected, param) VALUES(?, ?, ?, ?, ?, ?, ?);",
325 (
326 chattype,
327 &grpname,
328 grpid,
329 create_blocked,
330 timestamp,
331 create_protected,
332 param.unwrap_or_default(),
333 ),
334 ).await?;
335
336 let chat_id = ChatId::new(u32::try_from(row_id)?);
337
338 if create_protected == ProtectionStatus::Protected {
339 chat_id
340 .add_protection_msg(context, ProtectionStatus::Protected, None, timestamp)
341 .await?;
342 } else {
343 chat_id.maybe_add_encrypted_msg(context, timestamp).await?;
344 }
345
346 info!(
347 context,
348 "Created group/mailinglist '{}' grpid={} as {}, blocked={}, protected={create_protected}.",
349 &grpname,
350 grpid,
351 chat_id,
352 create_blocked,
353 );
354
355 Ok(chat_id)
356 }
357
358 async fn set_selfavatar_timestamp(self, context: &Context, timestamp: i64) -> Result<()> {
359 context
360 .sql
361 .execute(
362 "UPDATE contacts
363 SET selfavatar_sent=?
364 WHERE id IN(SELECT contact_id FROM chats_contacts WHERE chat_id=? AND add_timestamp >= remove_timestamp)",
365 (timestamp, self),
366 )
367 .await?;
368 Ok(())
369 }
370
371 pub(crate) async fn set_blocked(self, context: &Context, new_blocked: Blocked) -> Result<bool> {
375 if self.is_special() {
376 bail!("ignoring setting of Block-status for {}", self);
377 }
378 let count = context
379 .sql
380 .execute(
381 "UPDATE chats SET blocked=?1 WHERE id=?2 AND blocked != ?1",
382 (new_blocked, self),
383 )
384 .await?;
385 Ok(count > 0)
386 }
387
388 pub async fn block(self, context: &Context) -> Result<()> {
390 self.block_ex(context, Sync).await
391 }
392
393 pub(crate) async fn block_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
394 let chat = Chat::load_from_db(context, self).await?;
395 let mut delete = false;
396
397 match chat.typ {
398 Chattype::OutBroadcast => {
399 bail!("Can't block chat of type {:?}", chat.typ)
400 }
401 Chattype::Single => {
402 for contact_id in get_chat_contacts(context, self).await? {
403 if contact_id != ContactId::SELF {
404 info!(
405 context,
406 "Blocking the contact {contact_id} to block 1:1 chat."
407 );
408 contact::set_blocked(context, Nosync, contact_id, true).await?;
409 }
410 }
411 }
412 Chattype::Group => {
413 info!(context, "Can't block groups yet, deleting the chat.");
414 delete = true;
415 }
416 Chattype::Mailinglist | Chattype::InBroadcast => {
417 if self.set_blocked(context, Blocked::Yes).await? {
418 context.emit_event(EventType::ChatModified(self));
419 }
420 }
421 }
422 chatlist_events::emit_chatlist_changed(context);
423
424 if sync.into() {
425 chat.sync(context, SyncAction::Block)
427 .await
428 .log_err(context)
429 .ok();
430 }
431 if delete {
432 self.delete_ex(context, Nosync).await?;
433 }
434 Ok(())
435 }
436
437 pub async fn unblock(self, context: &Context) -> Result<()> {
439 self.unblock_ex(context, Sync).await
440 }
441
442 pub(crate) async fn unblock_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
443 self.set_blocked(context, Blocked::Not).await?;
444
445 chatlist_events::emit_chatlist_changed(context);
446
447 if sync.into() {
448 let chat = Chat::load_from_db(context, self).await?;
449 chat.sync(context, SyncAction::Unblock)
453 .await
454 .log_err(context)
455 .ok();
456 }
457
458 Ok(())
459 }
460
461 pub async fn accept(self, context: &Context) -> Result<()> {
465 self.accept_ex(context, Sync).await
466 }
467
468 pub(crate) async fn accept_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
469 let chat = Chat::load_from_db(context, self).await?;
470
471 match chat.typ {
472 Chattype::Single | Chattype::Group | Chattype::OutBroadcast | Chattype::InBroadcast => {
473 for contact_id in get_chat_contacts(context, self).await? {
478 if contact_id != ContactId::SELF {
479 ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat)
480 .await?;
481 }
482 }
483 }
484 Chattype::Mailinglist => {
485 }
487 }
488
489 if self.set_blocked(context, Blocked::Not).await? {
490 context.emit_event(EventType::ChatModified(self));
491 chatlist_events::emit_chatlist_item_changed(context, self);
492 }
493
494 if sync.into() {
495 chat.sync(context, SyncAction::Accept)
496 .await
497 .log_err(context)
498 .ok();
499 }
500 Ok(())
501 }
502
503 pub(crate) async fn inner_set_protection(
507 self,
508 context: &Context,
509 protect: ProtectionStatus,
510 ) -> Result<bool> {
511 ensure!(!self.is_special(), "Invalid chat-id {self}.");
512
513 let chat = Chat::load_from_db(context, self).await?;
514
515 if protect == chat.protected {
516 info!(context, "Protection status unchanged for {}.", self);
517 return Ok(false);
518 }
519
520 match protect {
521 ProtectionStatus::Protected => match chat.typ {
522 Chattype::Single
523 | Chattype::Group
524 | Chattype::OutBroadcast
525 | Chattype::InBroadcast => {}
526 Chattype::Mailinglist => bail!("Cannot protect mailing lists"),
527 },
528 ProtectionStatus::Unprotected => {}
529 };
530
531 context
532 .sql
533 .execute("UPDATE chats SET protected=? WHERE id=?;", (protect, self))
534 .await?;
535
536 context.emit_event(EventType::ChatModified(self));
537 chatlist_events::emit_chatlist_item_changed(context, self);
538
539 self.reset_gossiped_timestamp(context).await?;
541
542 Ok(true)
543 }
544
545 pub(crate) async fn add_protection_msg(
553 self,
554 context: &Context,
555 protect: ProtectionStatus,
556 contact_id: Option<ContactId>,
557 timestamp_sort: i64,
558 ) -> Result<()> {
559 if contact_id == Some(ContactId::SELF) {
560 return Ok(());
565 }
566
567 let text = context.stock_protection_msg(protect, contact_id).await;
568 let cmd = match protect {
569 ProtectionStatus::Protected => SystemMessage::ChatProtectionEnabled,
570 ProtectionStatus::Unprotected => SystemMessage::ChatProtectionDisabled,
571 };
572 add_info_msg_with_cmd(
573 context,
574 self,
575 &text,
576 cmd,
577 timestamp_sort,
578 None,
579 None,
580 None,
581 None,
582 )
583 .await?;
584
585 Ok(())
586 }
587
588 async fn maybe_add_encrypted_msg(self, context: &Context, timestamp_sort: i64) -> Result<()> {
593 let chat = Chat::load_from_db(context, self).await?;
594
595 if !chat.is_encrypted(context).await?
599 || self <= DC_CHAT_ID_LAST_SPECIAL
600 || chat.is_device_talk()
601 || chat.is_self_talk()
602 || (!chat.can_send(context).await? && !chat.is_contact_request())
603 || chat.blocked == Blocked::Yes
604 {
605 return Ok(());
606 }
607
608 let text = stock_str::messages_e2e_encrypted(context).await;
609 add_info_msg_with_cmd(
610 context,
611 self,
612 &text,
613 SystemMessage::ChatE2ee,
614 timestamp_sort,
615 None,
616 None,
617 None,
618 None,
619 )
620 .await?;
621 Ok(())
622 }
623
624 async fn set_protection_for_timestamp_sort(
629 self,
630 context: &Context,
631 protect: ProtectionStatus,
632 timestamp_sort: i64,
633 contact_id: Option<ContactId>,
634 ) -> Result<()> {
635 let protection_status_modified = self
636 .inner_set_protection(context, protect)
637 .await
638 .with_context(|| format!("Cannot set protection for {self}"))?;
639 if protection_status_modified {
640 self.add_protection_msg(context, protect, contact_id, timestamp_sort)
641 .await?;
642 chatlist_events::emit_chatlist_item_changed(context, self);
643 }
644 Ok(())
645 }
646
647 pub(crate) async fn set_protection(
651 self,
652 context: &Context,
653 protect: ProtectionStatus,
654 timestamp_sent: i64,
655 contact_id: Option<ContactId>,
656 ) -> Result<()> {
657 let sort_to_bottom = true;
658 let (received, incoming) = (false, false);
659 let ts = self
660 .calc_sort_timestamp(context, timestamp_sent, sort_to_bottom, received, incoming)
661 .await?
662 .saturating_add(1);
665 self.set_protection_for_timestamp_sort(context, protect, ts, contact_id)
666 .await
667 }
668
669 pub(crate) async fn set_protection_for_contact(
674 context: &Context,
675 contact_id: ContactId,
676 timestamp: i64,
677 ) -> Result<()> {
678 let chat_id = ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Yes)
679 .await
680 .with_context(|| format!("can't create chat for {contact_id}"))?;
681 chat_id
682 .set_protection(
683 context,
684 ProtectionStatus::Protected,
685 timestamp,
686 Some(contact_id),
687 )
688 .await?;
689 Ok(())
690 }
691
692 pub async fn set_visibility(self, context: &Context, visibility: ChatVisibility) -> Result<()> {
694 self.set_visibility_ex(context, Sync, visibility).await
695 }
696
697 pub(crate) async fn set_visibility_ex(
698 self,
699 context: &Context,
700 sync: sync::Sync,
701 visibility: ChatVisibility,
702 ) -> Result<()> {
703 ensure!(
704 !self.is_special(),
705 "bad chat_id, can not be special chat: {}",
706 self
707 );
708
709 context
710 .sql
711 .transaction(move |transaction| {
712 if visibility == ChatVisibility::Archived {
713 transaction.execute(
714 "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;",
715 (MessageState::InNoticed, self, MessageState::InFresh),
716 )?;
717 }
718 transaction.execute(
719 "UPDATE chats SET archived=? WHERE id=?;",
720 (visibility, self),
721 )?;
722 Ok(())
723 })
724 .await?;
725
726 if visibility == ChatVisibility::Archived {
727 start_chat_ephemeral_timers(context, self).await?;
728 }
729
730 context.emit_msgs_changed_without_ids();
731 chatlist_events::emit_chatlist_changed(context);
732 chatlist_events::emit_chatlist_item_changed(context, self);
733
734 if sync.into() {
735 let chat = Chat::load_from_db(context, self).await?;
736 chat.sync(context, SyncAction::SetVisibility(visibility))
737 .await
738 .log_err(context)
739 .ok();
740 }
741 Ok(())
742 }
743
744 pub async fn unarchive_if_not_muted(
752 self,
753 context: &Context,
754 msg_state: MessageState,
755 ) -> Result<()> {
756 if msg_state != MessageState::InFresh {
757 context
758 .sql
759 .execute(
760 "UPDATE chats SET archived=0 WHERE id=? AND archived=1 \
761 AND NOT(muted_until=-1 OR muted_until>?)",
762 (self, time()),
763 )
764 .await?;
765 return Ok(());
766 }
767 let chat = Chat::load_from_db(context, self).await?;
768 if chat.visibility != ChatVisibility::Archived {
769 return Ok(());
770 }
771 if chat.is_muted() {
772 let unread_cnt = context
773 .sql
774 .count(
775 "SELECT COUNT(*)
776 FROM msgs
777 WHERE state=?
778 AND hidden=0
779 AND chat_id=?",
780 (MessageState::InFresh, self),
781 )
782 .await?;
783 if unread_cnt == 1 {
784 context.emit_msgs_changed_without_msg_id(DC_CHAT_ID_ARCHIVED_LINK);
786 }
787 return Ok(());
788 }
789 context
790 .sql
791 .execute("UPDATE chats SET archived=0 WHERE id=?", (self,))
792 .await?;
793 Ok(())
794 }
795
796 pub(crate) fn emit_msg_event(self, context: &Context, msg_id: MsgId, important: bool) {
799 if important {
800 debug_assert!(!msg_id.is_unset());
801
802 context.emit_incoming_msg(self, msg_id);
803 } else {
804 context.emit_msgs_changed(self, msg_id);
805 }
806 }
807
808 pub async fn delete(self, context: &Context) -> Result<()> {
810 self.delete_ex(context, Sync).await
811 }
812
813 pub(crate) async fn delete_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
814 ensure!(
815 !self.is_special(),
816 "bad chat_id, can not be a special chat: {}",
817 self
818 );
819
820 let chat = Chat::load_from_db(context, self).await?;
821 let delete_msgs_target = context.get_delete_msgs_target().await?;
822 let sync_id = match sync {
823 Nosync => None,
824 Sync => chat.get_sync_id(context).await?,
825 };
826
827 context
828 .sql
829 .transaction(|transaction| {
830 transaction.execute(
831 "UPDATE imap SET target=? WHERE rfc724_mid IN (SELECT rfc724_mid FROM msgs WHERE chat_id=?)",
832 (delete_msgs_target, self,),
833 )?;
834 transaction.execute(
835 "DELETE FROM smtp WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?)",
836 (self,),
837 )?;
838 transaction.execute(
839 "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?)",
840 (self,),
841 )?;
842 transaction.execute("DELETE FROM msgs WHERE chat_id=?", (self,))?;
843 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (self,))?;
844 transaction.execute("DELETE FROM chats WHERE id=?", (self,))?;
845 Ok(())
846 })
847 .await?;
848
849 context.emit_event(EventType::ChatDeleted { chat_id: self });
850 context.emit_msgs_changed_without_ids();
851
852 if let Some(id) = sync_id {
853 self::sync(context, id, SyncAction::Delete)
854 .await
855 .log_err(context)
856 .ok();
857 }
858
859 if chat.is_self_talk() {
860 let mut msg = Message::new_text(stock_str::self_deleted_msg_body(context).await);
861 add_device_msg(context, None, Some(&mut msg)).await?;
862 }
863 chatlist_events::emit_chatlist_changed(context);
864
865 context
866 .set_config_internal(Config::LastHousekeeping, None)
867 .await?;
868 context.scheduler.interrupt_inbox().await;
869
870 Ok(())
871 }
872
873 pub async fn set_draft(self, context: &Context, mut msg: Option<&mut Message>) -> Result<()> {
877 if self.is_special() {
878 return Ok(());
879 }
880
881 let changed = match &mut msg {
882 None => self.maybe_delete_draft(context).await?,
883 Some(msg) => self.do_set_draft(context, msg).await?,
884 };
885
886 if changed {
887 if msg.is_some() {
888 match self.get_draft_msg_id(context).await? {
889 Some(msg_id) => context.emit_msgs_changed(self, msg_id),
890 None => context.emit_msgs_changed_without_msg_id(self),
891 }
892 } else {
893 context.emit_msgs_changed_without_msg_id(self)
894 }
895 }
896
897 Ok(())
898 }
899
900 async fn get_draft_msg_id(self, context: &Context) -> Result<Option<MsgId>> {
902 let msg_id: Option<MsgId> = context
903 .sql
904 .query_get_value(
905 "SELECT id FROM msgs WHERE chat_id=? AND state=?;",
906 (self, MessageState::OutDraft),
907 )
908 .await?;
909 Ok(msg_id)
910 }
911
912 pub async fn get_draft(self, context: &Context) -> Result<Option<Message>> {
914 if self.is_special() {
915 return Ok(None);
916 }
917 match self.get_draft_msg_id(context).await? {
918 Some(draft_msg_id) => {
919 let msg = Message::load_from_db(context, draft_msg_id).await?;
920 Ok(Some(msg))
921 }
922 None => Ok(None),
923 }
924 }
925
926 async fn maybe_delete_draft(self, context: &Context) -> Result<bool> {
930 Ok(context
931 .sql
932 .execute(
933 "DELETE FROM msgs WHERE chat_id=? AND state=?",
934 (self, MessageState::OutDraft),
935 )
936 .await?
937 > 0)
938 }
939
940 async fn do_set_draft(self, context: &Context, msg: &mut Message) -> Result<bool> {
943 match msg.viewtype {
944 Viewtype::Unknown => bail!("Can not set draft of unknown type."),
945 Viewtype::Text => {
946 if msg.text.is_empty() && msg.in_reply_to.is_none_or_empty() {
947 bail!("No text and no quote in draft");
948 }
949 }
950 _ => {
951 if msg.viewtype == Viewtype::File {
952 if let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg)
953 .filter(|&(vt, _)| vt == Viewtype::Webxdc || vt == Viewtype::Vcard)
958 {
959 msg.viewtype = better_type;
960 }
961 }
962 if msg.viewtype == Viewtype::Vcard {
963 let blob = msg
964 .param
965 .get_file_blob(context)?
966 .context("no file stored in params")?;
967 msg.try_set_vcard(context, &blob.to_abs_path()).await?;
968 }
969 }
970 }
971
972 msg.state = MessageState::OutDraft;
975 msg.chat_id = self;
976
977 if !msg.id.is_special() {
979 if let Some(old_draft) = self.get_draft(context).await? {
980 if old_draft.id == msg.id
981 && old_draft.chat_id == self
982 && old_draft.state == MessageState::OutDraft
983 {
984 let affected_rows = context
985 .sql.execute(
986 "UPDATE msgs
987 SET timestamp=?1,type=?2,txt=?3,txt_normalized=?4,param=?5,mime_in_reply_to=?6
988 WHERE id=?7
989 AND (type <> ?2
990 OR txt <> ?3
991 OR txt_normalized <> ?4
992 OR param <> ?5
993 OR mime_in_reply_to <> ?6);",
994 (
995 time(),
996 msg.viewtype,
997 &msg.text,
998 message::normalize_text(&msg.text),
999 msg.param.to_string(),
1000 msg.in_reply_to.as_deref().unwrap_or_default(),
1001 msg.id,
1002 ),
1003 ).await?;
1004 return Ok(affected_rows > 0);
1005 }
1006 }
1007 }
1008
1009 let row_id = context
1010 .sql
1011 .transaction(|transaction| {
1012 transaction.execute(
1014 "DELETE FROM msgs WHERE chat_id=? AND state=?",
1015 (self, MessageState::OutDraft),
1016 )?;
1017
1018 transaction.execute(
1020 "INSERT INTO msgs (
1021 chat_id,
1022 rfc724_mid,
1023 from_id,
1024 timestamp,
1025 type,
1026 state,
1027 txt,
1028 txt_normalized,
1029 param,
1030 hidden,
1031 mime_in_reply_to)
1032 VALUES (?,?,?,?,?,?,?,?,?,?,?);",
1033 (
1034 self,
1035 &msg.rfc724_mid,
1036 ContactId::SELF,
1037 time(),
1038 msg.viewtype,
1039 MessageState::OutDraft,
1040 &msg.text,
1041 message::normalize_text(&msg.text),
1042 msg.param.to_string(),
1043 1,
1044 msg.in_reply_to.as_deref().unwrap_or_default(),
1045 ),
1046 )?;
1047
1048 Ok(transaction.last_insert_rowid())
1049 })
1050 .await?;
1051 msg.id = MsgId::new(row_id.try_into()?);
1052 Ok(true)
1053 }
1054
1055 pub async fn get_msg_cnt(self, context: &Context) -> Result<usize> {
1057 let count = context
1058 .sql
1059 .count(
1060 "SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id=?",
1061 (self,),
1062 )
1063 .await?;
1064 Ok(count)
1065 }
1066
1067 pub async fn get_fresh_msg_cnt(self, context: &Context) -> Result<usize> {
1069 let count = if self.is_archived_link() {
1080 context
1081 .sql
1082 .count(
1083 "SELECT COUNT(DISTINCT(m.chat_id))
1084 FROM msgs m
1085 LEFT JOIN chats c ON m.chat_id=c.id
1086 WHERE m.state=10
1087 and m.hidden=0
1088 AND m.chat_id>9
1089 AND c.blocked=0
1090 AND c.archived=1
1091 ",
1092 (),
1093 )
1094 .await?
1095 } else {
1096 context
1097 .sql
1098 .count(
1099 "SELECT COUNT(*)
1100 FROM msgs
1101 WHERE state=?
1102 AND hidden=0
1103 AND chat_id=?;",
1104 (MessageState::InFresh, self),
1105 )
1106 .await?
1107 };
1108 Ok(count)
1109 }
1110
1111 pub(crate) async fn created_timestamp(self, context: &Context) -> Result<i64> {
1112 Ok(context
1113 .sql
1114 .query_get_value("SELECT created_timestamp FROM chats WHERE id=?", (self,))
1115 .await?
1116 .unwrap_or(0))
1117 }
1118
1119 pub(crate) async fn get_timestamp(self, context: &Context) -> Result<Option<i64>> {
1122 let timestamp = context
1123 .sql
1124 .query_get_value(
1125 "SELECT MAX(timestamp)
1126 FROM msgs
1127 WHERE chat_id=?
1128 HAVING COUNT(*) > 0",
1129 (self,),
1130 )
1131 .await?;
1132 Ok(timestamp)
1133 }
1134
1135 pub async fn get_similar_chat_ids(self, context: &Context) -> Result<Vec<(ChatId, f64)>> {
1141 let intersection: Vec<(ChatId, f64)> = context
1143 .sql
1144 .query_map(
1145 "SELECT y.chat_id, SUM(x.contact_id = y.contact_id)
1146 FROM chats_contacts as x
1147 JOIN chats_contacts as y
1148 WHERE x.contact_id > 9
1149 AND y.contact_id > 9
1150 AND x.add_timestamp >= x.remove_timestamp
1151 AND y.add_timestamp >= y.remove_timestamp
1152 AND x.chat_id=?
1153 AND y.chat_id<>x.chat_id
1154 AND y.chat_id>?
1155 GROUP BY y.chat_id",
1156 (self, DC_CHAT_ID_LAST_SPECIAL),
1157 |row| {
1158 let chat_id: ChatId = row.get(0)?;
1159 let intersection: f64 = row.get(1)?;
1160 Ok((chat_id, intersection))
1161 },
1162 |rows| {
1163 rows.collect::<std::result::Result<Vec<_>, _>>()
1164 .map_err(Into::into)
1165 },
1166 )
1167 .await
1168 .context("failed to calculate member set intersections")?;
1169
1170 let chat_size: HashMap<ChatId, f64> = context
1171 .sql
1172 .query_map(
1173 "SELECT chat_id, count(*) AS n
1174 FROM chats_contacts
1175 WHERE contact_id > ? AND chat_id > ?
1176 AND add_timestamp >= remove_timestamp
1177 GROUP BY chat_id",
1178 (ContactId::LAST_SPECIAL, DC_CHAT_ID_LAST_SPECIAL),
1179 |row| {
1180 let chat_id: ChatId = row.get(0)?;
1181 let size: f64 = row.get(1)?;
1182 Ok((chat_id, size))
1183 },
1184 |rows| {
1185 rows.collect::<std::result::Result<HashMap<ChatId, f64>, _>>()
1186 .map_err(Into::into)
1187 },
1188 )
1189 .await
1190 .context("failed to count chat member sizes")?;
1191
1192 let our_chat_size = chat_size.get(&self).copied().unwrap_or_default();
1193 let mut chats_with_metrics = Vec::new();
1194 for (chat_id, intersection_size) in intersection {
1195 if intersection_size > 0.0 {
1196 let other_chat_size = chat_size.get(&chat_id).copied().unwrap_or_default();
1197 let union_size = our_chat_size + other_chat_size - intersection_size;
1198 let metric = intersection_size / union_size;
1199 chats_with_metrics.push((chat_id, metric))
1200 }
1201 }
1202 chats_with_metrics.sort_unstable_by(|(chat_id1, metric1), (chat_id2, metric2)| {
1203 metric2
1204 .partial_cmp(metric1)
1205 .unwrap_or(chat_id2.cmp(chat_id1))
1206 });
1207
1208 let mut res = Vec::new();
1210 let now = time();
1211 for (chat_id, metric) in chats_with_metrics {
1212 if let Some(chat_timestamp) = chat_id.get_timestamp(context).await? {
1213 if now > chat_timestamp + 42 * 24 * 3600 {
1214 continue;
1216 }
1217 }
1218
1219 if metric < 0.1 {
1220 break;
1222 }
1223
1224 let chat = Chat::load_from_db(context, chat_id).await?;
1225 if chat.typ != Chattype::Group {
1226 continue;
1227 }
1228
1229 match chat.visibility {
1230 ChatVisibility::Normal | ChatVisibility::Pinned => {}
1231 ChatVisibility::Archived => continue,
1232 }
1233
1234 res.push((chat_id, metric));
1235 if res.len() >= 5 {
1236 break;
1237 }
1238 }
1239
1240 Ok(res)
1241 }
1242
1243 pub async fn get_similar_chatlist(self, context: &Context) -> Result<Chatlist> {
1247 let chat_ids: Vec<ChatId> = self
1248 .get_similar_chat_ids(context)
1249 .await
1250 .context("failed to get similar chat IDs")?
1251 .into_iter()
1252 .map(|(chat_id, _metric)| chat_id)
1253 .collect();
1254 let chatlist = Chatlist::from_chat_ids(context, &chat_ids).await?;
1255 Ok(chatlist)
1256 }
1257
1258 pub(crate) async fn get_param(self, context: &Context) -> Result<Params> {
1259 let res: Option<String> = context
1260 .sql
1261 .query_get_value("SELECT param FROM chats WHERE id=?", (self,))
1262 .await?;
1263 Ok(res
1264 .map(|s| s.parse().unwrap_or_default())
1265 .unwrap_or_default())
1266 }
1267
1268 pub(crate) async fn is_unpromoted(self, context: &Context) -> Result<bool> {
1270 let param = self.get_param(context).await?;
1271 let unpromoted = param.get_bool(Param::Unpromoted).unwrap_or_default();
1272 Ok(unpromoted)
1273 }
1274
1275 pub(crate) async fn is_promoted(self, context: &Context) -> Result<bool> {
1277 let promoted = !self.is_unpromoted(context).await?;
1278 Ok(promoted)
1279 }
1280
1281 pub async fn is_self_talk(self, context: &Context) -> Result<bool> {
1283 Ok(self.get_param(context).await?.exists(Param::Selftalk))
1284 }
1285
1286 pub async fn is_device_talk(self, context: &Context) -> Result<bool> {
1288 Ok(self.get_param(context).await?.exists(Param::Devicetalk))
1289 }
1290
1291 async fn parent_query<T, F>(
1292 self,
1293 context: &Context,
1294 fields: &str,
1295 state_out_min: MessageState,
1296 f: F,
1297 ) -> Result<Option<T>>
1298 where
1299 F: Send + FnOnce(&rusqlite::Row) -> rusqlite::Result<T>,
1300 T: Send + 'static,
1301 {
1302 let sql = &context.sql;
1303 let query = format!(
1304 "SELECT {fields} \
1305 FROM msgs \
1306 WHERE chat_id=? \
1307 AND ((state BETWEEN {} AND {}) OR (state >= {})) \
1308 AND NOT hidden \
1309 AND download_state={} \
1310 AND from_id != {} \
1311 ORDER BY timestamp DESC, id DESC \
1312 LIMIT 1;",
1313 MessageState::InFresh as u32,
1314 MessageState::InSeen as u32,
1315 state_out_min as u32,
1316 DownloadState::Done as u32,
1319 ContactId::INFO.to_u32(),
1322 );
1323 sql.query_row_optional(&query, (self,), f).await
1324 }
1325
1326 async fn get_parent_mime_headers(
1327 self,
1328 context: &Context,
1329 state_out_min: MessageState,
1330 ) -> Result<Option<(String, String, String)>> {
1331 self.parent_query(
1332 context,
1333 "rfc724_mid, mime_in_reply_to, IFNULL(mime_references, '')",
1334 state_out_min,
1335 |row: &rusqlite::Row| {
1336 let rfc724_mid: String = row.get(0)?;
1337 let mime_in_reply_to: String = row.get(1)?;
1338 let mime_references: String = row.get(2)?;
1339 Ok((rfc724_mid, mime_in_reply_to, mime_references))
1340 },
1341 )
1342 .await
1343 }
1344
1345 pub async fn get_encryption_info(self, context: &Context) -> Result<String> {
1353 let chat = Chat::load_from_db(context, self).await?;
1354 if !chat.is_encrypted(context).await? {
1355 return Ok(stock_str::encr_none(context).await);
1356 }
1357
1358 let mut ret = stock_str::e2e_available(context).await + "\n";
1359
1360 for &contact_id in get_chat_contacts(context, self)
1361 .await?
1362 .iter()
1363 .filter(|&contact_id| !contact_id.is_special())
1364 {
1365 let contact = Contact::get_by_id(context, contact_id).await?;
1366 let addr = contact.get_addr();
1367 logged_debug_assert!(
1368 context,
1369 contact.is_key_contact(),
1370 "get_encryption_info: contact {contact_id} is not a key-contact."
1371 );
1372 let fingerprint = contact
1373 .fingerprint()
1374 .context("Contact does not have a fingerprint in encrypted chat")?;
1375 if contact.public_key(context).await?.is_some() {
1376 ret += &format!("\n{addr}\n{fingerprint}\n");
1377 } else {
1378 ret += &format!("\n{addr}\n(key missing)\n{fingerprint}\n");
1379 }
1380 }
1381
1382 Ok(ret.trim().to_string())
1383 }
1384
1385 pub fn to_u32(self) -> u32 {
1390 self.0
1391 }
1392
1393 pub(crate) async fn reset_gossiped_timestamp(self, context: &Context) -> Result<()> {
1394 context
1395 .sql
1396 .execute("DELETE FROM gossip_timestamp WHERE chat_id=?", (self,))
1397 .await?;
1398 Ok(())
1399 }
1400
1401 pub async fn is_protected(self, context: &Context) -> Result<ProtectionStatus> {
1403 let protection_status = context
1404 .sql
1405 .query_get_value("SELECT protected FROM chats WHERE id=?", (self,))
1406 .await?
1407 .unwrap_or_default();
1408 Ok(protection_status)
1409 }
1410
1411 pub(crate) async fn calc_sort_timestamp(
1420 self,
1421 context: &Context,
1422 message_timestamp: i64,
1423 always_sort_to_bottom: bool,
1424 received: bool,
1425 incoming: bool,
1426 ) -> Result<i64> {
1427 let mut sort_timestamp = cmp::min(message_timestamp, smeared_time(context));
1428
1429 let last_msg_time: Option<i64> = if always_sort_to_bottom {
1430 context
1436 .sql
1437 .query_get_value(
1438 "SELECT MAX(timestamp)
1439 FROM msgs
1440 WHERE chat_id=? AND state!=?
1441 HAVING COUNT(*) > 0",
1442 (self, MessageState::OutDraft),
1443 )
1444 .await?
1445 } else if received {
1446 context
1457 .sql
1458 .query_row_optional(
1459 "SELECT MAX(timestamp), MAX(IIF(state=?,timestamp_sent,0))
1460 FROM msgs
1461 WHERE chat_id=? AND hidden=0 AND state>?
1462 HAVING COUNT(*) > 0",
1463 (MessageState::InSeen, self, MessageState::InFresh),
1464 |row| {
1465 let ts: i64 = row.get(0)?;
1466 let ts_sent_seen: i64 = row.get(1)?;
1467 Ok((ts, ts_sent_seen))
1468 },
1469 )
1470 .await?
1471 .and_then(|(ts, ts_sent_seen)| {
1472 match incoming || ts_sent_seen <= message_timestamp {
1473 true => Some(ts),
1474 false => None,
1475 }
1476 })
1477 } else {
1478 None
1479 };
1480
1481 if let Some(last_msg_time) = last_msg_time {
1482 if last_msg_time > sort_timestamp {
1483 sort_timestamp = last_msg_time;
1484 }
1485 }
1486
1487 Ok(sort_timestamp)
1488 }
1489}
1490
1491impl std::fmt::Display for ChatId {
1492 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1493 if self.is_trash() {
1494 write!(f, "Chat#Trash")
1495 } else if self.is_archived_link() {
1496 write!(f, "Chat#ArchivedLink")
1497 } else if self.is_alldone_hint() {
1498 write!(f, "Chat#AlldoneHint")
1499 } else if self.is_special() {
1500 write!(f, "Chat#Special{}", self.0)
1501 } else {
1502 write!(f, "Chat#{}", self.0)
1503 }
1504 }
1505}
1506
1507impl rusqlite::types::ToSql for ChatId {
1512 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
1513 let val = rusqlite::types::Value::Integer(i64::from(self.0));
1514 let out = rusqlite::types::ToSqlOutput::Owned(val);
1515 Ok(out)
1516 }
1517}
1518
1519impl rusqlite::types::FromSql for ChatId {
1521 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
1522 i64::column_result(value).and_then(|val| {
1523 if 0 <= val && val <= i64::from(u32::MAX) {
1524 Ok(ChatId::new(val as u32))
1525 } else {
1526 Err(rusqlite::types::FromSqlError::OutOfRange(val))
1527 }
1528 })
1529 }
1530}
1531
1532#[derive(Debug, Clone, Deserialize, Serialize)]
1537pub struct Chat {
1538 pub id: ChatId,
1540
1541 pub typ: Chattype,
1543
1544 pub name: String,
1546
1547 pub visibility: ChatVisibility,
1549
1550 pub grpid: String,
1553
1554 pub blocked: Blocked,
1556
1557 pub param: Params,
1559
1560 is_sending_locations: bool,
1562
1563 pub mute_duration: MuteDuration,
1565
1566 pub(crate) protected: ProtectionStatus,
1568}
1569
1570impl Chat {
1571 pub async fn load_from_db(context: &Context, chat_id: ChatId) -> Result<Self> {
1573 let mut chat = context
1574 .sql
1575 .query_row(
1576 "SELECT c.type, c.name, c.grpid, c.param, c.archived,
1577 c.blocked, c.locations_send_until, c.muted_until, c.protected
1578 FROM chats c
1579 WHERE c.id=?;",
1580 (chat_id,),
1581 |row| {
1582 let c = Chat {
1583 id: chat_id,
1584 typ: row.get(0)?,
1585 name: row.get::<_, String>(1)?,
1586 grpid: row.get::<_, String>(2)?,
1587 param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
1588 visibility: row.get(4)?,
1589 blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(),
1590 is_sending_locations: row.get(6)?,
1591 mute_duration: row.get(7)?,
1592 protected: row.get(8)?,
1593 };
1594 Ok(c)
1595 },
1596 )
1597 .await
1598 .context(format!("Failed loading chat {chat_id} from database"))?;
1599
1600 if chat.id.is_archived_link() {
1601 chat.name = stock_str::archived_chats(context).await;
1602 } else {
1603 if chat.typ == Chattype::Single && chat.name.is_empty() {
1604 let mut chat_name = "Err [Name not found]".to_owned();
1607 match get_chat_contacts(context, chat.id).await {
1608 Ok(contacts) => {
1609 if let Some(contact_id) = contacts.first() {
1610 if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1611 contact.get_display_name().clone_into(&mut chat_name);
1612 }
1613 }
1614 }
1615 Err(err) => {
1616 error!(
1617 context,
1618 "Failed to load contacts for {}: {:#}.", chat.id, err
1619 );
1620 }
1621 }
1622 chat.name = chat_name;
1623 }
1624 if chat.param.exists(Param::Selftalk) {
1625 chat.name = stock_str::saved_messages(context).await;
1626 } else if chat.param.exists(Param::Devicetalk) {
1627 chat.name = stock_str::device_messages(context).await;
1628 }
1629 }
1630
1631 Ok(chat)
1632 }
1633
1634 pub fn is_self_talk(&self) -> bool {
1636 self.param.exists(Param::Selftalk)
1637 }
1638
1639 pub fn is_device_talk(&self) -> bool {
1641 self.param.exists(Param::Devicetalk)
1642 }
1643
1644 pub fn is_mailing_list(&self) -> bool {
1646 self.typ == Chattype::Mailinglist
1647 }
1648
1649 pub(crate) async fn why_cant_send(&self, context: &Context) -> Result<Option<CantSendReason>> {
1653 self.why_cant_send_ex(context, &|_| false).await
1654 }
1655
1656 pub(crate) async fn why_cant_send_ex(
1657 &self,
1658 context: &Context,
1659 skip_fn: &(dyn Send + Sync + Fn(&CantSendReason) -> bool),
1660 ) -> Result<Option<CantSendReason>> {
1661 use CantSendReason::*;
1662 if self.id.is_special() {
1665 let reason = SpecialChat;
1666 if !skip_fn(&reason) {
1667 return Ok(Some(reason));
1668 }
1669 }
1670 if self.is_device_talk() {
1671 let reason = DeviceChat;
1672 if !skip_fn(&reason) {
1673 return Ok(Some(reason));
1674 }
1675 }
1676 if self.is_contact_request() {
1677 let reason = ContactRequest;
1678 if !skip_fn(&reason) {
1679 return Ok(Some(reason));
1680 }
1681 }
1682 if self.is_mailing_list() && self.get_mailinglist_addr().is_none_or_empty() {
1683 let reason = ReadOnlyMailingList;
1684 if !skip_fn(&reason) {
1685 return Ok(Some(reason));
1686 }
1687 }
1688 if self.typ == Chattype::InBroadcast {
1689 let reason = InBroadcast;
1690 if !skip_fn(&reason) {
1691 return Ok(Some(reason));
1692 }
1693 }
1694
1695 let reason = NotAMember;
1697 if !skip_fn(&reason) && !self.is_self_in_chat(context).await? {
1698 return Ok(Some(reason));
1699 }
1700
1701 let reason = MissingKey;
1702 if !skip_fn(&reason) && self.typ == Chattype::Single {
1703 let contact_ids = get_chat_contacts(context, self.id).await?;
1704 if let Some(contact_id) = contact_ids.first() {
1705 let contact = Contact::get_by_id(context, *contact_id).await?;
1706 if contact.is_key_contact() && contact.public_key(context).await?.is_none() {
1707 return Ok(Some(reason));
1708 }
1709 }
1710 }
1711
1712 Ok(None)
1713 }
1714
1715 pub async fn can_send(&self, context: &Context) -> Result<bool> {
1719 Ok(self.why_cant_send(context).await?.is_none())
1720 }
1721
1722 pub(crate) async fn is_self_in_chat(&self, context: &Context) -> Result<bool> {
1726 match self.typ {
1727 Chattype::Single | Chattype::OutBroadcast | Chattype::Mailinglist => Ok(true),
1728 Chattype::Group => is_contact_in_chat(context, self.id, ContactId::SELF).await,
1729 Chattype::InBroadcast => Ok(false),
1730 }
1731 }
1732
1733 pub(crate) async fn update_param(&mut self, context: &Context) -> Result<()> {
1734 context
1735 .sql
1736 .execute(
1737 "UPDATE chats SET param=? WHERE id=?",
1738 (self.param.to_string(), self.id),
1739 )
1740 .await?;
1741 Ok(())
1742 }
1743
1744 pub fn get_id(&self) -> ChatId {
1746 self.id
1747 }
1748
1749 pub fn get_type(&self) -> Chattype {
1751 self.typ
1752 }
1753
1754 pub fn get_name(&self) -> &str {
1756 &self.name
1757 }
1758
1759 pub fn get_mailinglist_addr(&self) -> Option<&str> {
1761 self.param.get(Param::ListPost)
1762 }
1763
1764 pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1766 if self.id.is_archived_link() {
1767 return Ok(Some(get_archive_icon(context).await?));
1770 } else if self.is_device_talk() {
1771 return Ok(Some(get_device_icon(context).await?));
1772 } else if self.is_self_talk() {
1773 return Ok(Some(get_saved_messages_icon(context).await?));
1774 } else if !self.is_encrypted(context).await? {
1775 return Ok(Some(get_abs_path(
1777 context,
1778 Path::new(&get_unencrypted_icon(context).await?),
1779 )));
1780 } else if self.typ == Chattype::Single {
1781 let contacts = get_chat_contacts(context, self.id).await?;
1785 if let Some(contact_id) = contacts.first() {
1786 let contact = Contact::get_by_id(context, *contact_id).await?;
1787 return contact.get_profile_image(context).await;
1788 }
1789 } else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
1790 if !image_rel.is_empty() {
1792 return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
1793 }
1794 }
1795 Ok(None)
1796 }
1797
1798 pub async fn get_color(&self, context: &Context) -> Result<u32> {
1804 let mut color = 0;
1805
1806 if self.typ == Chattype::Single {
1807 let contacts = get_chat_contacts(context, self.id).await?;
1808 if let Some(contact_id) = contacts.first() {
1809 if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1810 color = contact.get_color();
1811 }
1812 }
1813 } else if !self.grpid.is_empty() {
1814 color = str_to_color(&self.grpid);
1815 } else {
1816 color = str_to_color(&self.name);
1817 }
1818
1819 Ok(color)
1820 }
1821
1822 pub async fn get_info(&self, context: &Context) -> Result<ChatInfo> {
1827 let draft = match self.id.get_draft(context).await? {
1828 Some(message) => message.text,
1829 _ => String::new(),
1830 };
1831 Ok(ChatInfo {
1832 id: self.id,
1833 type_: self.typ as u32,
1834 name: self.name.clone(),
1835 archived: self.visibility == ChatVisibility::Archived,
1836 param: self.param.to_string(),
1837 is_sending_locations: self.is_sending_locations,
1838 color: self.get_color(context).await?,
1839 profile_image: self
1840 .get_profile_image(context)
1841 .await?
1842 .unwrap_or_else(std::path::PathBuf::new),
1843 draft,
1844 is_muted: self.is_muted(),
1845 ephemeral_timer: self.id.get_ephemeral_timer(context).await?,
1846 })
1847 }
1848
1849 pub fn get_visibility(&self) -> ChatVisibility {
1851 self.visibility
1852 }
1853
1854 pub fn is_contact_request(&self) -> bool {
1859 self.blocked == Blocked::Request
1860 }
1861
1862 pub fn is_unpromoted(&self) -> bool {
1864 self.param.get_bool(Param::Unpromoted).unwrap_or_default()
1865 }
1866
1867 pub fn is_promoted(&self) -> bool {
1870 !self.is_unpromoted()
1871 }
1872
1873 pub fn is_protected(&self) -> bool {
1884 self.protected == ProtectionStatus::Protected
1885 }
1886
1887 pub async fn is_encrypted(&self, context: &Context) -> Result<bool> {
1889 let is_encrypted = self.is_protected()
1890 || match self.typ {
1891 Chattype::Single => {
1892 match context
1893 .sql
1894 .query_row_optional(
1895 "SELECT cc.contact_id, c.fingerprint<>''
1896 FROM chats_contacts cc LEFT JOIN contacts c
1897 ON c.id=cc.contact_id
1898 WHERE cc.chat_id=?
1899 ",
1900 (self.id,),
1901 |row| {
1902 let id: ContactId = row.get(0)?;
1903 let is_key: bool = row.get(1)?;
1904 Ok((id, is_key))
1905 },
1906 )
1907 .await?
1908 {
1909 Some((id, is_key)) => is_key || id == ContactId::DEVICE,
1910 None => true,
1911 }
1912 }
1913 Chattype::Group => {
1914 !self.grpid.is_empty()
1916 }
1917 Chattype::Mailinglist => false,
1918 Chattype::OutBroadcast | Chattype::InBroadcast => true,
1919 };
1920 Ok(is_encrypted)
1921 }
1922
1923 pub fn is_sending_locations(&self) -> bool {
1925 self.is_sending_locations
1926 }
1927
1928 pub fn is_muted(&self) -> bool {
1930 match self.mute_duration {
1931 MuteDuration::NotMuted => false,
1932 MuteDuration::Forever => true,
1933 MuteDuration::Until(when) => when > SystemTime::now(),
1934 }
1935 }
1936
1937 pub(crate) async fn member_list_timestamp(&self, context: &Context) -> Result<i64> {
1939 if let Some(member_list_timestamp) = self.param.get_i64(Param::MemberListTimestamp) {
1940 Ok(member_list_timestamp)
1941 } else {
1942 Ok(self.id.created_timestamp(context).await?)
1943 }
1944 }
1945
1946 pub(crate) async fn member_list_is_stale(&self, context: &Context) -> Result<bool> {
1952 let now = time();
1953 let member_list_ts = self.member_list_timestamp(context).await?;
1954 let is_stale = now.saturating_add(TIMESTAMP_SENT_TOLERANCE)
1955 >= member_list_ts.saturating_add(60 * 24 * 3600);
1956 Ok(is_stale)
1957 }
1958
1959 async fn prepare_msg_raw(
1965 &mut self,
1966 context: &Context,
1967 msg: &mut Message,
1968 update_msg_id: Option<MsgId>,
1969 ) -> Result<()> {
1970 let mut to_id = 0;
1971 let mut location_id = 0;
1972
1973 if msg.rfc724_mid.is_empty() {
1974 msg.rfc724_mid = create_outgoing_rfc724_mid();
1975 }
1976
1977 if self.typ == Chattype::Single {
1978 if let Some(id) = context
1979 .sql
1980 .query_get_value(
1981 "SELECT contact_id FROM chats_contacts WHERE chat_id=?;",
1982 (self.id,),
1983 )
1984 .await?
1985 {
1986 to_id = id;
1987 } else {
1988 error!(
1989 context,
1990 "Cannot send message, contact for {} not found.", self.id,
1991 );
1992 bail!("Cannot set message, contact for {} not found.", self.id);
1993 }
1994 } else if matches!(self.typ, Chattype::Group | Chattype::OutBroadcast)
1995 && self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
1996 {
1997 msg.param.set_int(Param::AttachGroupImage, 1);
1998 self.param
1999 .remove(Param::Unpromoted)
2000 .set_i64(Param::GroupNameTimestamp, msg.timestamp_sort);
2001 self.update_param(context).await?;
2002 context
2008 .sync_qr_code_tokens(Some(self.grpid.as_str()))
2009 .await
2010 .log_err(context)
2011 .ok();
2012 }
2013
2014 let is_bot = context.get_config_bool(Config::Bot).await?;
2015 msg.param
2016 .set_optional(Param::Bot, Some("1").filter(|_| is_bot));
2017
2018 let new_references;
2022 if self.is_self_talk() {
2023 new_references = String::new();
2026 } else if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
2027 self
2033 .id
2034 .get_parent_mime_headers(context, MessageState::OutPending)
2035 .await?
2036 {
2037 if msg.in_reply_to.is_none() && !parent_rfc724_mid.is_empty() {
2041 msg.in_reply_to = Some(parent_rfc724_mid.clone());
2042 }
2043
2044 let parent_references = if parent_references.is_empty() {
2054 parent_in_reply_to
2055 } else {
2056 parent_references
2057 };
2058
2059 let mut references_vec: Vec<&str> = parent_references.rsplit(' ').take(2).collect();
2062 references_vec.reverse();
2063
2064 if !parent_rfc724_mid.is_empty()
2065 && !references_vec.contains(&parent_rfc724_mid.as_str())
2066 {
2067 references_vec.push(&parent_rfc724_mid)
2068 }
2069
2070 if references_vec.is_empty() {
2071 new_references = msg.rfc724_mid.clone();
2074 } else {
2075 new_references = references_vec.join(" ");
2076 }
2077 } else {
2078 new_references = msg.rfc724_mid.clone();
2084 }
2085
2086 if msg.param.exists(Param::SetLatitude) {
2088 if let Ok(row_id) = context
2089 .sql
2090 .insert(
2091 "INSERT INTO locations \
2092 (timestamp,from_id,chat_id, latitude,longitude,independent)\
2093 VALUES (?,?,?, ?,?,1);",
2094 (
2095 msg.timestamp_sort,
2096 ContactId::SELF,
2097 self.id,
2098 msg.param.get_float(Param::SetLatitude).unwrap_or_default(),
2099 msg.param.get_float(Param::SetLongitude).unwrap_or_default(),
2100 ),
2101 )
2102 .await
2103 {
2104 location_id = row_id;
2105 }
2106 }
2107
2108 let ephemeral_timer = if msg.param.get_cmd() == SystemMessage::EphemeralTimerChanged {
2109 EphemeralTimer::Disabled
2110 } else {
2111 self.id.get_ephemeral_timer(context).await?
2112 };
2113 let ephemeral_timestamp = match ephemeral_timer {
2114 EphemeralTimer::Disabled => 0,
2115 EphemeralTimer::Enabled { duration } => time().saturating_add(duration.into()),
2116 };
2117
2118 let (msg_text, was_truncated) = truncate_msg_text(context, msg.text.clone()).await?;
2119 let new_mime_headers = if msg.has_html() {
2120 if msg.param.exists(Param::Forwarded) {
2121 msg.get_id().get_html(context).await?
2122 } else {
2123 msg.param.get(Param::SendHtml).map(|s| s.to_string())
2124 }
2125 } else {
2126 None
2127 };
2128 let new_mime_headers: Option<String> = new_mime_headers.map(|s| {
2129 let html_part = MimePart::new("text/html", s);
2130 let mut buffer = Vec::new();
2131 let cursor = Cursor::new(&mut buffer);
2132 html_part.write_part(cursor).ok();
2133 String::from_utf8_lossy(&buffer).to_string()
2134 });
2135 let new_mime_headers = new_mime_headers.or_else(|| match was_truncated {
2136 true => Some("Content-Type: text/plain; charset=utf-8\r\n\r\n".to_string() + &msg.text),
2140 false => None,
2141 });
2142 let new_mime_headers = match new_mime_headers {
2143 Some(h) => Some(tokio::task::block_in_place(move || {
2144 buf_compress(h.as_bytes())
2145 })?),
2146 None => None,
2147 };
2148
2149 msg.chat_id = self.id;
2150 msg.from_id = ContactId::SELF;
2151
2152 if let Some(update_msg_id) = update_msg_id {
2154 context
2155 .sql
2156 .execute(
2157 "UPDATE msgs
2158 SET rfc724_mid=?, chat_id=?, from_id=?, to_id=?, timestamp=?, type=?,
2159 state=?, txt=?, txt_normalized=?, subject=?, param=?,
2160 hidden=?, mime_in_reply_to=?, mime_references=?, mime_modified=?,
2161 mime_headers=?, mime_compressed=1, location_id=?, ephemeral_timer=?,
2162 ephemeral_timestamp=?
2163 WHERE id=?;",
2164 params_slice![
2165 msg.rfc724_mid,
2166 msg.chat_id,
2167 msg.from_id,
2168 to_id,
2169 msg.timestamp_sort,
2170 msg.viewtype,
2171 msg.state,
2172 msg_text,
2173 message::normalize_text(&msg_text),
2174 &msg.subject,
2175 msg.param.to_string(),
2176 msg.hidden,
2177 msg.in_reply_to.as_deref().unwrap_or_default(),
2178 new_references,
2179 new_mime_headers.is_some(),
2180 new_mime_headers.unwrap_or_default(),
2181 location_id as i32,
2182 ephemeral_timer,
2183 ephemeral_timestamp,
2184 update_msg_id
2185 ],
2186 )
2187 .await?;
2188 msg.id = update_msg_id;
2189 } else {
2190 let raw_id = context
2191 .sql
2192 .insert(
2193 "INSERT INTO msgs (
2194 rfc724_mid,
2195 chat_id,
2196 from_id,
2197 to_id,
2198 timestamp,
2199 type,
2200 state,
2201 txt,
2202 txt_normalized,
2203 subject,
2204 param,
2205 hidden,
2206 mime_in_reply_to,
2207 mime_references,
2208 mime_modified,
2209 mime_headers,
2210 mime_compressed,
2211 location_id,
2212 ephemeral_timer,
2213 ephemeral_timestamp)
2214 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,?,?,?);",
2215 params_slice![
2216 msg.rfc724_mid,
2217 msg.chat_id,
2218 msg.from_id,
2219 to_id,
2220 msg.timestamp_sort,
2221 msg.viewtype,
2222 msg.state,
2223 msg_text,
2224 message::normalize_text(&msg_text),
2225 &msg.subject,
2226 msg.param.to_string(),
2227 msg.hidden,
2228 msg.in_reply_to.as_deref().unwrap_or_default(),
2229 new_references,
2230 new_mime_headers.is_some(),
2231 new_mime_headers.unwrap_or_default(),
2232 location_id as i32,
2233 ephemeral_timer,
2234 ephemeral_timestamp
2235 ],
2236 )
2237 .await?;
2238 context.new_msgs_notify.notify_one();
2239 msg.id = MsgId::new(u32::try_from(raw_id)?);
2240
2241 maybe_set_logging_xdc(context, msg, self.id).await?;
2242 context
2243 .update_webxdc_integration_database(msg, context)
2244 .await?;
2245 }
2246 context.scheduler.interrupt_ephemeral_task().await;
2247 Ok(())
2248 }
2249
2250 pub(crate) async fn sync_contacts(&self, context: &Context) -> Result<()> {
2252 if self.is_encrypted(context).await? {
2253 let fingerprint_addrs = context
2254 .sql
2255 .query_map(
2256 "SELECT c.fingerprint, c.addr
2257 FROM contacts c INNER JOIN chats_contacts cc
2258 ON c.id=cc.contact_id
2259 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
2260 (self.id,),
2261 |row| {
2262 let fingerprint = row.get(0)?;
2263 let addr = row.get(1)?;
2264 Ok((fingerprint, addr))
2265 },
2266 |addrs| addrs.collect::<Result<Vec<_>, _>>().map_err(Into::into),
2267 )
2268 .await?;
2269 self.sync(context, SyncAction::SetPgpContacts(fingerprint_addrs))
2270 .await?;
2271 } else {
2272 let addrs = context
2273 .sql
2274 .query_map(
2275 "SELECT c.addr \
2276 FROM contacts c INNER JOIN chats_contacts cc \
2277 ON c.id=cc.contact_id \
2278 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
2279 (self.id,),
2280 |row| row.get::<_, String>(0),
2281 |addrs| addrs.collect::<Result<Vec<_>, _>>().map_err(Into::into),
2282 )
2283 .await?;
2284 self.sync(context, SyncAction::SetContacts(addrs)).await?;
2285 }
2286 Ok(())
2287 }
2288
2289 async fn get_sync_id(&self, context: &Context) -> Result<Option<SyncId>> {
2291 match self.typ {
2292 Chattype::Single => {
2293 if self.is_device_talk() {
2294 return Ok(Some(SyncId::Device));
2295 }
2296
2297 let mut r = None;
2298 for contact_id in get_chat_contacts(context, self.id).await? {
2299 if contact_id == ContactId::SELF && !self.is_self_talk() {
2300 continue;
2301 }
2302 if r.is_some() {
2303 return Ok(None);
2304 }
2305 let contact = Contact::get_by_id(context, contact_id).await?;
2306 if let Some(fingerprint) = contact.fingerprint() {
2307 r = Some(SyncId::ContactFingerprint(fingerprint.hex()));
2308 } else {
2309 r = Some(SyncId::ContactAddr(contact.get_addr().to_string()));
2310 }
2311 }
2312 Ok(r)
2313 }
2314 Chattype::OutBroadcast
2315 | Chattype::InBroadcast
2316 | Chattype::Group
2317 | Chattype::Mailinglist => {
2318 if !self.grpid.is_empty() {
2319 return Ok(Some(SyncId::Grpid(self.grpid.clone())));
2320 }
2321
2322 let Some((parent_rfc724_mid, parent_in_reply_to, _)) = self
2323 .id
2324 .get_parent_mime_headers(context, MessageState::OutDelivered)
2325 .await?
2326 else {
2327 warn!(
2328 context,
2329 "Chat::get_sync_id({}): No good message identifying the chat found.",
2330 self.id
2331 );
2332 return Ok(None);
2333 };
2334 Ok(Some(SyncId::Msgids(vec![
2335 parent_in_reply_to,
2336 parent_rfc724_mid,
2337 ])))
2338 }
2339 }
2340 }
2341
2342 pub(crate) async fn sync(&self, context: &Context, action: SyncAction) -> Result<()> {
2344 if let Some(id) = self.get_sync_id(context).await? {
2345 sync(context, id, action).await?;
2346 }
2347 Ok(())
2348 }
2349}
2350
2351pub(crate) async fn sync(context: &Context, id: SyncId, action: SyncAction) -> Result<()> {
2352 context
2353 .add_sync_item(SyncData::AlterChat { id, action })
2354 .await?;
2355 context.scheduler.interrupt_inbox().await;
2356 Ok(())
2357}
2358
2359#[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize, EnumIter)]
2361#[repr(i8)]
2362pub enum ChatVisibility {
2363 Normal = 0,
2365
2366 Archived = 1,
2368
2369 Pinned = 2,
2371}
2372
2373impl rusqlite::types::ToSql for ChatVisibility {
2374 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
2375 let val = rusqlite::types::Value::Integer(*self as i64);
2376 let out = rusqlite::types::ToSqlOutput::Owned(val);
2377 Ok(out)
2378 }
2379}
2380
2381impl rusqlite::types::FromSql for ChatVisibility {
2382 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
2383 i64::column_result(value).map(|val| {
2384 match val {
2385 2 => ChatVisibility::Pinned,
2386 1 => ChatVisibility::Archived,
2387 0 => ChatVisibility::Normal,
2388 _ => ChatVisibility::Normal,
2390 }
2391 })
2392 }
2393}
2394
2395#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2397#[non_exhaustive]
2398pub struct ChatInfo {
2399 pub id: ChatId,
2401
2402 #[serde(rename = "type")]
2409 pub type_: u32,
2410
2411 pub name: String,
2413
2414 pub archived: bool,
2416
2417 pub param: String,
2421
2422 pub is_sending_locations: bool,
2424
2425 pub color: u32,
2429
2430 pub profile_image: std::path::PathBuf,
2435
2436 pub draft: String,
2444
2445 pub is_muted: bool,
2449
2450 pub ephemeral_timer: EphemeralTimer,
2452 }
2458
2459async fn get_asset_icon(context: &Context, name: &str, bytes: &[u8]) -> Result<PathBuf> {
2460 ensure!(name.starts_with("icon-"));
2461 if let Some(icon) = context.sql.get_raw_config(name).await? {
2462 return Ok(get_abs_path(context, Path::new(&icon)));
2463 }
2464
2465 let blob =
2466 BlobObject::create_and_deduplicate_from_bytes(context, bytes, &format!("{name}.png"))?;
2467 let icon = blob.as_name().to_string();
2468 context.sql.set_raw_config(name, Some(&icon)).await?;
2469
2470 Ok(get_abs_path(context, Path::new(&icon)))
2471}
2472
2473pub(crate) async fn get_saved_messages_icon(context: &Context) -> Result<PathBuf> {
2474 get_asset_icon(
2475 context,
2476 "icon-saved-messages",
2477 include_bytes!("../assets/icon-saved-messages.png"),
2478 )
2479 .await
2480}
2481
2482pub(crate) async fn get_device_icon(context: &Context) -> Result<PathBuf> {
2483 get_asset_icon(
2484 context,
2485 "icon-device",
2486 include_bytes!("../assets/icon-device.png"),
2487 )
2488 .await
2489}
2490
2491pub(crate) async fn get_archive_icon(context: &Context) -> Result<PathBuf> {
2492 get_asset_icon(
2493 context,
2494 "icon-archive",
2495 include_bytes!("../assets/icon-archive.png"),
2496 )
2497 .await
2498}
2499
2500pub(crate) async fn get_unencrypted_icon(context: &Context) -> Result<PathBuf> {
2503 get_asset_icon(
2504 context,
2505 "icon-unencrypted",
2506 include_bytes!("../assets/icon-unencrypted.png"),
2507 )
2508 .await
2509}
2510
2511async fn update_special_chat_name(
2512 context: &Context,
2513 contact_id: ContactId,
2514 name: String,
2515) -> Result<()> {
2516 if let Some(ChatIdBlocked { id: chat_id, .. }) =
2517 ChatIdBlocked::lookup_by_contact(context, contact_id).await?
2518 {
2519 context
2521 .sql
2522 .execute(
2523 "UPDATE chats SET name=? WHERE id=? AND name!=?",
2524 (&name, chat_id, &name),
2525 )
2526 .await?;
2527 }
2528 Ok(())
2529}
2530
2531pub(crate) async fn update_special_chat_names(context: &Context) -> Result<()> {
2532 update_special_chat_name(
2533 context,
2534 ContactId::DEVICE,
2535 stock_str::device_messages(context).await,
2536 )
2537 .await?;
2538 update_special_chat_name(
2539 context,
2540 ContactId::SELF,
2541 stock_str::saved_messages(context).await,
2542 )
2543 .await?;
2544 Ok(())
2545}
2546
2547#[derive(Debug)]
2555pub(crate) struct ChatIdBlocked {
2556 pub id: ChatId,
2558
2559 pub blocked: Blocked,
2561}
2562
2563impl ChatIdBlocked {
2564 pub async fn lookup_by_contact(
2568 context: &Context,
2569 contact_id: ContactId,
2570 ) -> Result<Option<Self>> {
2571 ensure!(context.sql.is_open().await, "Database not available");
2572 ensure!(
2573 contact_id != ContactId::UNDEFINED,
2574 "Invalid contact id requested"
2575 );
2576
2577 context
2578 .sql
2579 .query_row_optional(
2580 "SELECT c.id, c.blocked
2581 FROM chats c
2582 INNER JOIN chats_contacts j
2583 ON c.id=j.chat_id
2584 WHERE c.type=100 -- 100 = Chattype::Single
2585 AND c.id>9 -- 9 = DC_CHAT_ID_LAST_SPECIAL
2586 AND j.contact_id=?;",
2587 (contact_id,),
2588 |row| {
2589 let id: ChatId = row.get(0)?;
2590 let blocked: Blocked = row.get(1)?;
2591 Ok(ChatIdBlocked { id, blocked })
2592 },
2593 )
2594 .await
2595 }
2596
2597 pub async fn get_for_contact(
2602 context: &Context,
2603 contact_id: ContactId,
2604 create_blocked: Blocked,
2605 ) -> Result<Self> {
2606 ensure!(context.sql.is_open().await, "Database not available");
2607 ensure!(
2608 contact_id != ContactId::UNDEFINED,
2609 "Invalid contact id requested"
2610 );
2611
2612 if let Some(res) = Self::lookup_by_contact(context, contact_id).await? {
2613 return Ok(res);
2615 }
2616
2617 let contact = Contact::get_by_id(context, contact_id).await?;
2618 let chat_name = contact.get_display_name().to_string();
2619 let mut params = Params::new();
2620 match contact_id {
2621 ContactId::SELF => {
2622 params.set_int(Param::Selftalk, 1);
2623 }
2624 ContactId::DEVICE => {
2625 params.set_int(Param::Devicetalk, 1);
2626 }
2627 _ => (),
2628 }
2629
2630 let protected = contact_id == ContactId::SELF || contact.is_verified(context).await?;
2631 let smeared_time = create_smeared_timestamp(context);
2632
2633 let chat_id = context
2634 .sql
2635 .transaction(move |transaction| {
2636 transaction.execute(
2637 "INSERT INTO chats
2638 (type, name, param, blocked, created_timestamp, protected)
2639 VALUES(?, ?, ?, ?, ?, ?)",
2640 (
2641 Chattype::Single,
2642 chat_name,
2643 params.to_string(),
2644 create_blocked as u8,
2645 smeared_time,
2646 if protected {
2647 ProtectionStatus::Protected
2648 } else {
2649 ProtectionStatus::Unprotected
2650 },
2651 ),
2652 )?;
2653 let chat_id = ChatId::new(
2654 transaction
2655 .last_insert_rowid()
2656 .try_into()
2657 .context("chat table rowid overflows u32")?,
2658 );
2659
2660 transaction.execute(
2661 "INSERT INTO chats_contacts
2662 (chat_id, contact_id)
2663 VALUES((SELECT last_insert_rowid()), ?)",
2664 (contact_id,),
2665 )?;
2666
2667 Ok(chat_id)
2668 })
2669 .await?;
2670
2671 if protected {
2672 chat_id
2673 .add_protection_msg(
2674 context,
2675 ProtectionStatus::Protected,
2676 Some(contact_id),
2677 smeared_time,
2678 )
2679 .await?;
2680 } else {
2681 chat_id
2682 .maybe_add_encrypted_msg(context, smeared_time)
2683 .await?;
2684 }
2685
2686 Ok(Self {
2687 id: chat_id,
2688 blocked: create_blocked,
2689 })
2690 }
2691}
2692
2693async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
2694 if msg.viewtype == Viewtype::Text
2695 || msg.viewtype == Viewtype::VideochatInvitation
2696 || msg.viewtype == Viewtype::Call
2697 {
2698 } else if msg.viewtype.has_file() {
2700 let viewtype_orig = msg.viewtype;
2701 let mut blob = msg
2702 .param
2703 .get_file_blob(context)?
2704 .with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?;
2705 let mut maybe_image = false;
2706
2707 if msg.viewtype == Viewtype::File
2708 || msg.viewtype == Viewtype::Image
2709 || msg.viewtype == Viewtype::Sticker && !msg.param.exists(Param::ForceSticker)
2710 {
2711 if let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg) {
2718 if msg.viewtype == Viewtype::Sticker {
2719 if better_type != Viewtype::Image {
2720 msg.param.set_int(Param::ForceSticker, 1);
2722 }
2723 } else if better_type == Viewtype::Image {
2724 maybe_image = true;
2725 } else if better_type != Viewtype::Webxdc
2726 || context
2727 .ensure_sendable_webxdc_file(&blob.to_abs_path())
2728 .await
2729 .is_ok()
2730 {
2731 msg.viewtype = better_type;
2732 }
2733 }
2734 } else if msg.viewtype == Viewtype::Webxdc {
2735 context
2736 .ensure_sendable_webxdc_file(&blob.to_abs_path())
2737 .await?;
2738 }
2739
2740 if msg.viewtype == Viewtype::Vcard {
2741 msg.try_set_vcard(context, &blob.to_abs_path()).await?;
2742 }
2743 if msg.viewtype == Viewtype::File && maybe_image
2744 || msg.viewtype == Viewtype::Image
2745 || msg.viewtype == Viewtype::Sticker && !msg.param.exists(Param::ForceSticker)
2746 {
2747 let new_name = blob
2748 .check_or_recode_image(context, msg.get_filename(), &mut msg.viewtype)
2749 .await?;
2750 msg.param.set(Param::Filename, new_name);
2751 msg.param.set(Param::File, blob.as_name());
2752 }
2753
2754 if !msg.param.exists(Param::MimeType) {
2755 if let Some((viewtype, mime)) = message::guess_msgtype_from_suffix(msg) {
2756 let mime = match viewtype != Viewtype::Image
2759 || matches!(msg.viewtype, Viewtype::Image | Viewtype::Sticker)
2760 {
2761 true => mime,
2762 false => "application/octet-stream",
2763 };
2764 msg.param.set(Param::MimeType, mime);
2765 }
2766 }
2767
2768 msg.try_calc_and_set_dimensions(context).await?;
2769
2770 let filename = msg.get_filename().context("msg has no file")?;
2771 let suffix = Path::new(&filename)
2772 .extension()
2773 .and_then(|e| e.to_str())
2774 .unwrap_or("dat");
2775 let filename: String = match viewtype_orig {
2779 Viewtype::Voice => format!(
2780 "voice-messsage_{}.{}",
2781 chrono::Utc
2782 .timestamp_opt(msg.timestamp_sort, 0)
2783 .single()
2784 .map_or_else(
2785 || "YY-mm-dd_hh:mm:ss".to_string(),
2786 |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string()
2787 ),
2788 &suffix
2789 ),
2790 Viewtype::Image | Viewtype::Gif => format!(
2791 "image_{}.{}",
2792 chrono::Utc
2793 .timestamp_opt(msg.timestamp_sort, 0)
2794 .single()
2795 .map_or_else(
2796 || "YY-mm-dd_hh:mm:ss".to_string(),
2797 |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string(),
2798 ),
2799 &suffix,
2800 ),
2801 Viewtype::Video => format!(
2802 "video_{}.{}",
2803 chrono::Utc
2804 .timestamp_opt(msg.timestamp_sort, 0)
2805 .single()
2806 .map_or_else(
2807 || "YY-mm-dd_hh:mm:ss".to_string(),
2808 |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string()
2809 ),
2810 &suffix
2811 ),
2812 _ => filename,
2813 };
2814 msg.param.set(Param::Filename, filename);
2815
2816 info!(
2817 context,
2818 "Attaching \"{}\" for message type #{}.",
2819 blob.to_abs_path().display(),
2820 msg.viewtype
2821 );
2822 } else {
2823 bail!("Cannot send messages of type #{}.", msg.viewtype);
2824 }
2825 Ok(())
2826}
2827
2828pub async fn is_contact_in_chat(
2830 context: &Context,
2831 chat_id: ChatId,
2832 contact_id: ContactId,
2833) -> Result<bool> {
2834 let exists = context
2840 .sql
2841 .exists(
2842 "SELECT COUNT(*) FROM chats_contacts
2843 WHERE chat_id=? AND contact_id=?
2844 AND add_timestamp >= remove_timestamp",
2845 (chat_id, contact_id),
2846 )
2847 .await?;
2848 Ok(exists)
2849}
2850
2851pub async fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2858 ensure!(
2859 !chat_id.is_special(),
2860 "chat_id cannot be a special chat: {chat_id}"
2861 );
2862
2863 if msg.state != MessageState::Undefined && msg.state != MessageState::OutPreparing {
2864 msg.param.remove(Param::GuaranteeE2ee);
2865 msg.param.remove(Param::ForcePlaintext);
2866 msg.update_param(context).await?;
2867 }
2868
2869 if msg.is_system_message() {
2871 msg.text = sanitize_bidi_characters(&msg.text);
2872 }
2873
2874 if !prepare_send_msg(context, chat_id, msg).await?.is_empty() {
2875 if !msg.hidden {
2876 context.emit_msgs_changed(msg.chat_id, msg.id);
2877 }
2878
2879 if msg.param.exists(Param::SetLatitude) {
2880 context.emit_location_changed(Some(ContactId::SELF)).await?;
2881 }
2882
2883 context.scheduler.interrupt_smtp().await;
2884 }
2885
2886 Ok(msg.id)
2887}
2888
2889pub async fn send_msg_sync(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2894 let rowids = prepare_send_msg(context, chat_id, msg).await?;
2895 if rowids.is_empty() {
2896 return Ok(msg.id);
2897 }
2898 let mut smtp = crate::smtp::Smtp::new();
2899 for rowid in rowids {
2900 send_msg_to_smtp(context, &mut smtp, rowid)
2901 .await
2902 .context("failed to send message, queued for later sending")?;
2903 }
2904 context.emit_msgs_changed(msg.chat_id, msg.id);
2905 Ok(msg.id)
2906}
2907
2908async fn prepare_send_msg(
2912 context: &Context,
2913 chat_id: ChatId,
2914 msg: &mut Message,
2915) -> Result<Vec<i64>> {
2916 let mut chat = Chat::load_from_db(context, chat_id).await?;
2917
2918 let skip_fn = |reason: &CantSendReason| match reason {
2919 CantSendReason::ContactRequest => {
2920 msg.param.get_cmd() == SystemMessage::SecurejoinMessage
2923 }
2924 CantSendReason::NotAMember | CantSendReason::InBroadcast => {
2928 msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup
2929 }
2930 CantSendReason::MissingKey => msg
2931 .param
2932 .get_bool(Param::ForcePlaintext)
2933 .unwrap_or_default(),
2934 _ => false,
2935 };
2936 if let Some(reason) = chat.why_cant_send_ex(context, &skip_fn).await? {
2937 bail!("Cannot send to {chat_id}: {reason}");
2938 }
2939
2940 if chat.typ != Chattype::Single && !context.get_config_bool(Config::Bot).await? {
2945 if let Some(quoted_message) = msg.quoted_message(context).await? {
2946 if quoted_message.chat_id != chat_id {
2947 bail!(
2948 "Quote of message from {} cannot be sent to {chat_id}",
2949 quoted_message.chat_id
2950 );
2951 }
2952 }
2953 }
2954
2955 let update_msg_id = if msg.state == MessageState::OutDraft {
2957 msg.hidden = false;
2958 if !msg.id.is_special() && msg.chat_id == chat_id {
2959 Some(msg.id)
2960 } else {
2961 None
2962 }
2963 } else {
2964 None
2965 };
2966
2967 msg.state = MessageState::OutPending;
2969
2970 msg.timestamp_sort = create_smeared_timestamp(context);
2971 prepare_msg_blob(context, msg).await?;
2972 if !msg.hidden {
2973 chat_id.unarchive_if_not_muted(context, msg.state).await?;
2974 }
2975 chat.prepare_msg_raw(context, msg, update_msg_id).await?;
2976
2977 let row_ids = create_send_msg_jobs(context, msg)
2978 .await
2979 .context("Failed to create send jobs")?;
2980 if !row_ids.is_empty() {
2981 donation_request_maybe(context).await.log_err(context).ok();
2982 }
2983 Ok(row_ids)
2984}
2985
2986pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -> Result<Vec<i64>> {
2996 if msg.param.get_cmd() == SystemMessage::GroupNameChanged {
2997 msg.chat_id
2998 .update_timestamp(context, Param::GroupNameTimestamp, msg.timestamp_sort)
2999 .await?;
3000 }
3001
3002 let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
3003 let mimefactory = match MimeFactory::from_msg(context, msg.clone()).await {
3004 Ok(mf) => mf,
3005 Err(err) => {
3006 message::set_msg_failed(context, msg, &err.to_string())
3008 .await
3009 .ok();
3010 return Err(err);
3011 }
3012 };
3013 let attach_selfavatar = mimefactory.attach_selfavatar;
3014 let mut recipients = mimefactory.recipients();
3015
3016 let from = context.get_primary_self_addr().await?;
3017 let lowercase_from = from.to_lowercase();
3018
3019 recipients.retain(|x| x.to_lowercase() != lowercase_from);
3032 if (context.get_config_bool(Config::BccSelf).await?
3033 || msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage)
3034 && (context.get_config_delete_server_after().await? != Some(0) || !recipients.is_empty())
3035 {
3036 recipients.push(from);
3037 }
3038
3039 if msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden {
3041 recipients.clear();
3042 }
3043
3044 if recipients.is_empty() {
3045 info!(
3047 context,
3048 "Message {} has no recipient, skipping smtp-send.", msg.id
3049 );
3050 msg.param.set_int(Param::GuaranteeE2ee, 1);
3051 msg.update_param(context).await?;
3052 msg.id.set_delivered(context).await?;
3053 msg.state = MessageState::OutDelivered;
3054 return Ok(Vec::new());
3055 }
3056
3057 let rendered_msg = match mimefactory.render(context).await {
3058 Ok(res) => Ok(res),
3059 Err(err) => {
3060 message::set_msg_failed(context, msg, &err.to_string()).await?;
3061 Err(err)
3062 }
3063 }?;
3064
3065 if needs_encryption && !rendered_msg.is_encrypted {
3066 message::set_msg_failed(
3068 context,
3069 msg,
3070 "End-to-end-encryption unavailable unexpectedly.",
3071 )
3072 .await?;
3073 bail!(
3074 "e2e encryption unavailable {} - {:?}",
3075 msg.id,
3076 needs_encryption
3077 );
3078 }
3079
3080 let now = smeared_time(context);
3081
3082 if rendered_msg.last_added_location_id.is_some() {
3083 if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, now).await {
3084 error!(context, "Failed to set kml sent_timestamp: {err:#}.");
3085 }
3086 }
3087
3088 if attach_selfavatar {
3089 if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, now).await {
3090 error!(context, "Failed to set selfavatar timestamp: {err:#}.");
3091 }
3092 }
3093
3094 if rendered_msg.is_encrypted {
3095 msg.param.set_int(Param::GuaranteeE2ee, 1);
3096 } else {
3097 msg.param.remove(Param::GuaranteeE2ee);
3098 }
3099 msg.subject.clone_from(&rendered_msg.subject);
3100 context
3101 .sql
3102 .execute(
3103 "UPDATE msgs SET subject=?, param=? WHERE id=?",
3104 (&msg.subject, msg.param.to_string(), msg.id),
3105 )
3106 .await?;
3107
3108 let chunk_size = context.get_max_smtp_rcpt_to().await?;
3109 let trans_fn = |t: &mut rusqlite::Transaction| {
3110 let mut row_ids = Vec::<i64>::new();
3111 if let Some(sync_ids) = rendered_msg.sync_ids_to_delete {
3112 t.execute(
3113 &format!("DELETE FROM multi_device_sync WHERE id IN ({sync_ids})"),
3114 (),
3115 )?;
3116 t.execute(
3117 "INSERT INTO imap_send (mime, msg_id) VALUES (?, ?)",
3118 (&rendered_msg.message, msg.id),
3119 )?;
3120 } else {
3121 for recipients_chunk in recipients.chunks(chunk_size) {
3122 let recipients_chunk = recipients_chunk.join(" ");
3123 let row_id = t.execute(
3124 "INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id) \
3125 VALUES (?1, ?2, ?3, ?4)",
3126 (
3127 &rendered_msg.rfc724_mid,
3128 recipients_chunk,
3129 &rendered_msg.message,
3130 msg.id,
3131 ),
3132 )?;
3133 row_ids.push(row_id.try_into()?);
3134 }
3135 }
3136 Ok(row_ids)
3137 };
3138 context.sql.transaction(trans_fn).await
3139}
3140
3141pub async fn send_text_msg(
3145 context: &Context,
3146 chat_id: ChatId,
3147 text_to_send: String,
3148) -> Result<MsgId> {
3149 ensure!(
3150 !chat_id.is_special(),
3151 "bad chat_id, can not be a special chat: {}",
3152 chat_id
3153 );
3154
3155 let mut msg = Message::new_text(text_to_send);
3156 send_msg(context, chat_id, &mut msg).await
3157}
3158
3159pub async fn send_edit_request(context: &Context, msg_id: MsgId, new_text: String) -> Result<()> {
3161 let mut original_msg = Message::load_from_db(context, msg_id).await?;
3162 ensure!(
3163 original_msg.from_id == ContactId::SELF,
3164 "Can edit only own messages"
3165 );
3166 ensure!(!original_msg.is_info(), "Cannot edit info messages");
3167 ensure!(!original_msg.has_html(), "Cannot edit HTML messages");
3168 ensure!(
3169 original_msg.viewtype != Viewtype::VideochatInvitation,
3170 "Cannot edit videochat invitations"
3171 );
3172 ensure!(original_msg.viewtype != Viewtype::Call, "Cannot edit calls");
3173 ensure!(
3174 !original_msg.text.is_empty(), "Cannot add text"
3176 );
3177 ensure!(!new_text.trim().is_empty(), "Edited text cannot be empty");
3178 if original_msg.text == new_text {
3179 info!(context, "Text unchanged.");
3180 return Ok(());
3181 }
3182
3183 save_text_edit_to_db(context, &mut original_msg, &new_text).await?;
3184
3185 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() {
3188 edit_msg.param.set_int(Param::GuaranteeE2ee, 1);
3189 }
3190 edit_msg
3191 .param
3192 .set(Param::TextEditFor, original_msg.rfc724_mid);
3193 edit_msg.hidden = true;
3194 send_msg(context, original_msg.chat_id, &mut edit_msg).await?;
3195 Ok(())
3196}
3197
3198pub(crate) async fn save_text_edit_to_db(
3199 context: &Context,
3200 original_msg: &mut Message,
3201 new_text: &str,
3202) -> Result<()> {
3203 original_msg.param.set_int(Param::IsEdited, 1);
3204 context
3205 .sql
3206 .execute(
3207 "UPDATE msgs SET txt=?, txt_normalized=?, param=? WHERE id=?",
3208 (
3209 new_text,
3210 message::normalize_text(new_text),
3211 original_msg.param.to_string(),
3212 original_msg.id,
3213 ),
3214 )
3215 .await?;
3216 context.emit_msgs_changed(original_msg.chat_id, original_msg.id);
3217 Ok(())
3218}
3219
3220pub async fn send_videochat_invitation(context: &Context, chat_id: ChatId) -> Result<MsgId> {
3222 ensure!(
3223 !chat_id.is_special(),
3224 "video chat invitation cannot be sent to special chat: {}",
3225 chat_id
3226 );
3227
3228 let instance = if let Some(instance) = context.get_config(Config::WebrtcInstance).await? {
3229 if !instance.is_empty() {
3230 instance
3231 } else {
3232 bail!("webrtc_instance is empty");
3233 }
3234 } else {
3235 bail!("webrtc_instance not set");
3236 };
3237
3238 let instance = Message::create_webrtc_instance(&instance, &create_id());
3239
3240 let mut msg = Message::new(Viewtype::VideochatInvitation);
3241 msg.param.set(Param::WebrtcRoom, &instance);
3242 msg.text =
3243 stock_str::videochat_invite_msg_body(context, &Message::parse_webrtc_instance(&instance).1)
3244 .await;
3245 send_msg(context, chat_id, &mut msg).await
3246}
3247
3248async fn donation_request_maybe(context: &Context) -> Result<()> {
3249 let secs_between_checks = 30 * 24 * 60 * 60;
3250 let now = time();
3251 let ts = context
3252 .get_config_i64(Config::DonationRequestNextCheck)
3253 .await?;
3254 if ts > now {
3255 return Ok(());
3256 }
3257 let msg_cnt = context.sql.count(
3258 "SELECT COUNT(*) FROM msgs WHERE state>=? AND hidden=0",
3259 (MessageState::OutDelivered,),
3260 );
3261 let ts = if ts == 0 || msg_cnt.await? < 100 {
3262 now.saturating_add(secs_between_checks)
3263 } else {
3264 let mut msg = Message::new_text(stock_str::donation_request(context).await);
3265 add_device_msg(context, None, Some(&mut msg)).await?;
3266 i64::MAX
3267 };
3268 context
3269 .set_config_internal(Config::DonationRequestNextCheck, Some(&ts.to_string()))
3270 .await
3271}
3272
3273#[derive(Debug)]
3275pub struct MessageListOptions {
3276 pub info_only: bool,
3278
3279 pub add_daymarker: bool,
3281}
3282
3283pub async fn get_chat_msgs(context: &Context, chat_id: ChatId) -> Result<Vec<ChatItem>> {
3285 get_chat_msgs_ex(
3286 context,
3287 chat_id,
3288 MessageListOptions {
3289 info_only: false,
3290 add_daymarker: false,
3291 },
3292 )
3293 .await
3294}
3295
3296pub async fn get_chat_msgs_ex(
3298 context: &Context,
3299 chat_id: ChatId,
3300 options: MessageListOptions,
3301) -> Result<Vec<ChatItem>> {
3302 let MessageListOptions {
3303 info_only,
3304 add_daymarker,
3305 } = options;
3306 let process_row = if info_only {
3307 |row: &rusqlite::Row| {
3308 let params = row.get::<_, String>("param")?;
3310 let (from_id, to_id) = (
3311 row.get::<_, ContactId>("from_id")?,
3312 row.get::<_, ContactId>("to_id")?,
3313 );
3314 let is_info_msg: bool = from_id == ContactId::INFO
3315 || to_id == ContactId::INFO
3316 || match Params::from_str(¶ms) {
3317 Ok(p) => {
3318 let cmd = p.get_cmd();
3319 cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
3320 }
3321 _ => false,
3322 };
3323
3324 Ok((
3325 row.get::<_, i64>("timestamp")?,
3326 row.get::<_, MsgId>("id")?,
3327 !is_info_msg,
3328 ))
3329 }
3330 } else {
3331 |row: &rusqlite::Row| {
3332 Ok((
3333 row.get::<_, i64>("timestamp")?,
3334 row.get::<_, MsgId>("id")?,
3335 false,
3336 ))
3337 }
3338 };
3339 let process_rows = |rows: rusqlite::MappedRows<_>| {
3340 let mut sorted_rows = Vec::new();
3343 for row in rows {
3344 let (ts, curr_id, exclude_message): (i64, MsgId, bool) = row?;
3345 if !exclude_message {
3346 sorted_rows.push((ts, curr_id));
3347 }
3348 }
3349 sorted_rows.sort_unstable();
3350
3351 let mut ret = Vec::new();
3352 let mut last_day = 0;
3353 let cnv_to_local = gm2local_offset();
3354
3355 for (ts, curr_id) in sorted_rows {
3356 if add_daymarker {
3357 let curr_local_timestamp = ts + cnv_to_local;
3358 let secs_in_day = 86400;
3359 let curr_day = curr_local_timestamp / secs_in_day;
3360 if curr_day != last_day {
3361 ret.push(ChatItem::DayMarker {
3362 timestamp: curr_day * secs_in_day - cnv_to_local,
3363 });
3364 last_day = curr_day;
3365 }
3366 }
3367 ret.push(ChatItem::Message { msg_id: curr_id });
3368 }
3369 Ok(ret)
3370 };
3371
3372 let items = if info_only {
3373 context
3374 .sql
3375 .query_map(
3376 "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
3378 FROM msgs m
3379 WHERE m.chat_id=?
3380 AND m.hidden=0
3381 AND (
3382 m.param GLOB \"*S=*\"
3383 OR m.from_id == ?
3384 OR m.to_id == ?
3385 );",
3386 (chat_id, ContactId::INFO, ContactId::INFO),
3387 process_row,
3388 process_rows,
3389 )
3390 .await?
3391 } else {
3392 context
3393 .sql
3394 .query_map(
3395 "SELECT m.id AS id, m.timestamp AS timestamp
3396 FROM msgs m
3397 WHERE m.chat_id=?
3398 AND m.hidden=0;",
3399 (chat_id,),
3400 process_row,
3401 process_rows,
3402 )
3403 .await?
3404 };
3405 Ok(items)
3406}
3407
3408pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()> {
3411 if chat_id.is_archived_link() {
3414 let chat_ids_in_archive = context
3415 .sql
3416 .query_map(
3417 "SELECT DISTINCT(m.chat_id) FROM msgs m
3418 LEFT JOIN chats c ON m.chat_id=c.id
3419 WHERE m.state=10 AND m.hidden=0 AND m.chat_id>9 AND c.archived=1",
3420 (),
3421 |row| row.get::<_, ChatId>(0),
3422 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3423 )
3424 .await?;
3425 if chat_ids_in_archive.is_empty() {
3426 return Ok(());
3427 }
3428
3429 context
3430 .sql
3431 .transaction(|transaction| {
3432 let mut stmt = transaction.prepare(
3433 "UPDATE msgs SET state=13 WHERE state=10 AND hidden=0 AND chat_id = ?",
3434 )?;
3435 for chat_id_in_archive in &chat_ids_in_archive {
3436 stmt.execute((chat_id_in_archive,))?;
3437 }
3438 Ok(())
3439 })
3440 .await?;
3441
3442 for chat_id_in_archive in chat_ids_in_archive {
3443 start_chat_ephemeral_timers(context, chat_id_in_archive).await?;
3444 context.emit_event(EventType::MsgsNoticed(chat_id_in_archive));
3445 chatlist_events::emit_chatlist_item_changed(context, chat_id_in_archive);
3446 }
3447 } else {
3448 start_chat_ephemeral_timers(context, chat_id).await?;
3449
3450 let noticed_msgs_count = context
3451 .sql
3452 .execute(
3453 "UPDATE msgs
3454 SET state=?
3455 WHERE state=?
3456 AND hidden=0
3457 AND chat_id=?;",
3458 (MessageState::InNoticed, MessageState::InFresh, chat_id),
3459 )
3460 .await?;
3461
3462 let hidden_messages = context
3465 .sql
3466 .query_map(
3467 "SELECT id, rfc724_mid FROM msgs
3468 WHERE state=?
3469 AND hidden=1
3470 AND chat_id=?
3471 ORDER BY id LIMIT 100", (MessageState::InFresh, chat_id), |row| {
3474 let msg_id: MsgId = row.get(0)?;
3475 let rfc724_mid: String = row.get(1)?;
3476 Ok((msg_id, rfc724_mid))
3477 },
3478 |rows| {
3479 rows.collect::<std::result::Result<Vec<_>, _>>()
3480 .map_err(Into::into)
3481 },
3482 )
3483 .await?;
3484 for (msg_id, rfc724_mid) in &hidden_messages {
3485 message::update_msg_state(context, *msg_id, MessageState::InSeen).await?;
3486 imap::markseen_on_imap_table(context, rfc724_mid).await?;
3487 }
3488
3489 if noticed_msgs_count == 0 {
3490 return Ok(());
3491 }
3492 }
3493
3494 context.emit_event(EventType::MsgsNoticed(chat_id));
3495 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3496 context.on_archived_chats_maybe_noticed();
3497 Ok(())
3498}
3499
3500pub(crate) async fn mark_old_messages_as_noticed(
3507 context: &Context,
3508 mut msgs: Vec<ReceivedMsg>,
3509) -> Result<()> {
3510 msgs.retain(|m| m.state.is_outgoing());
3511 if msgs.is_empty() {
3512 return Ok(());
3513 }
3514
3515 let mut msgs_by_chat: HashMap<ChatId, ReceivedMsg> = HashMap::new();
3516 for msg in msgs {
3517 let chat_id = msg.chat_id;
3518 if let Some(existing_msg) = msgs_by_chat.get(&chat_id) {
3519 if msg.sort_timestamp > existing_msg.sort_timestamp {
3520 msgs_by_chat.insert(chat_id, msg);
3521 }
3522 } else {
3523 msgs_by_chat.insert(chat_id, msg);
3524 }
3525 }
3526
3527 let changed_chats = context
3528 .sql
3529 .transaction(|transaction| {
3530 let mut changed_chats = Vec::new();
3531 for (_, msg) in msgs_by_chat {
3532 let changed_rows = transaction.execute(
3533 "UPDATE msgs
3534 SET state=?
3535 WHERE state=?
3536 AND hidden=0
3537 AND chat_id=?
3538 AND timestamp<=?;",
3539 (
3540 MessageState::InNoticed,
3541 MessageState::InFresh,
3542 msg.chat_id,
3543 msg.sort_timestamp,
3544 ),
3545 )?;
3546 if changed_rows > 0 {
3547 changed_chats.push(msg.chat_id);
3548 }
3549 }
3550 Ok(changed_chats)
3551 })
3552 .await?;
3553
3554 if !changed_chats.is_empty() {
3555 info!(
3556 context,
3557 "Marking chats as noticed because there are newer outgoing messages: {changed_chats:?}."
3558 );
3559 context.on_archived_chats_maybe_noticed();
3560 }
3561
3562 for c in changed_chats {
3563 start_chat_ephemeral_timers(context, c).await?;
3564 context.emit_event(EventType::MsgsNoticed(c));
3565 chatlist_events::emit_chatlist_item_changed(context, c);
3566 }
3567
3568 Ok(())
3569}
3570
3571pub async fn get_chat_media(
3578 context: &Context,
3579 chat_id: Option<ChatId>,
3580 msg_type: Viewtype,
3581 msg_type2: Viewtype,
3582 msg_type3: Viewtype,
3583) -> Result<Vec<MsgId>> {
3584 let list = if msg_type == Viewtype::Webxdc
3585 && msg_type2 == Viewtype::Unknown
3586 && msg_type3 == Viewtype::Unknown
3587 {
3588 context
3589 .sql
3590 .query_map(
3591 "SELECT id
3592 FROM msgs
3593 WHERE (1=? OR chat_id=?)
3594 AND chat_id != ?
3595 AND type = ?
3596 AND hidden=0
3597 ORDER BY max(timestamp, timestamp_rcvd), id;",
3598 (
3599 chat_id.is_none(),
3600 chat_id.unwrap_or_else(|| ChatId::new(0)),
3601 DC_CHAT_ID_TRASH,
3602 Viewtype::Webxdc,
3603 ),
3604 |row| row.get::<_, MsgId>(0),
3605 |ids| Ok(ids.flatten().collect()),
3606 )
3607 .await?
3608 } else {
3609 context
3610 .sql
3611 .query_map(
3612 "SELECT id
3613 FROM msgs
3614 WHERE (1=? OR chat_id=?)
3615 AND chat_id != ?
3616 AND type IN (?, ?, ?)
3617 AND hidden=0
3618 ORDER BY timestamp, id;",
3619 (
3620 chat_id.is_none(),
3621 chat_id.unwrap_or_else(|| ChatId::new(0)),
3622 DC_CHAT_ID_TRASH,
3623 msg_type,
3624 if msg_type2 != Viewtype::Unknown {
3625 msg_type2
3626 } else {
3627 msg_type
3628 },
3629 if msg_type3 != Viewtype::Unknown {
3630 msg_type3
3631 } else {
3632 msg_type
3633 },
3634 ),
3635 |row| row.get::<_, MsgId>(0),
3636 |ids| Ok(ids.flatten().collect()),
3637 )
3638 .await?
3639 };
3640 Ok(list)
3641}
3642
3643pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3645 let list = context
3649 .sql
3650 .query_map(
3651 "SELECT cc.contact_id
3652 FROM chats_contacts cc
3653 LEFT JOIN contacts c
3654 ON c.id=cc.contact_id
3655 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp
3656 ORDER BY c.id=1, c.last_seen DESC, c.id DESC;",
3657 (chat_id,),
3658 |row| row.get::<_, ContactId>(0),
3659 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3660 )
3661 .await?;
3662
3663 Ok(list)
3664}
3665
3666pub async fn get_past_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3670 let now = time();
3671 let list = context
3672 .sql
3673 .query_map(
3674 "SELECT cc.contact_id
3675 FROM chats_contacts cc
3676 LEFT JOIN contacts c
3677 ON c.id=cc.contact_id
3678 WHERE cc.chat_id=?
3679 AND cc.add_timestamp < cc.remove_timestamp
3680 AND ? < cc.remove_timestamp
3681 ORDER BY c.id=1, cc.remove_timestamp DESC, c.id DESC",
3682 (chat_id, now.saturating_sub(60 * 24 * 3600)),
3683 |row| row.get::<_, ContactId>(0),
3684 |ids| ids.collect::<Result<Vec<_>, _>>().map_err(Into::into),
3685 )
3686 .await?;
3687
3688 Ok(list)
3689}
3690
3691pub async fn create_group_chat(
3694 context: &Context,
3695 protect: ProtectionStatus,
3696 name: &str,
3697) -> Result<ChatId> {
3698 create_group_ex(context, Some(protect), name).await
3699}
3700
3701pub async fn create_group_ex(
3706 context: &Context,
3707 encryption: Option<ProtectionStatus>,
3708 name: &str,
3709) -> Result<ChatId> {
3710 let mut chat_name = sanitize_single_line(name);
3711 if chat_name.is_empty() {
3712 error!(context, "Invalid chat name: {name}.");
3715 chat_name = "…".to_string();
3716 }
3717
3718 let grpid = match encryption {
3719 Some(_) => create_id(),
3720 None => String::new(),
3721 };
3722
3723 let timestamp = create_smeared_timestamp(context);
3724 let row_id = context
3725 .sql
3726 .insert(
3727 "INSERT INTO chats
3728 (type, name, grpid, param, created_timestamp)
3729 VALUES(?, ?, ?, \'U=1\', ?);",
3730 (Chattype::Group, chat_name, grpid, timestamp),
3731 )
3732 .await?;
3733
3734 let chat_id = ChatId::new(u32::try_from(row_id)?);
3735 add_to_chat_contacts_table(context, timestamp, chat_id, &[ContactId::SELF]).await?;
3736
3737 context.emit_msgs_changed_without_ids();
3738 chatlist_events::emit_chatlist_changed(context);
3739 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3740
3741 match encryption {
3742 Some(ProtectionStatus::Protected) => {
3743 let protect = ProtectionStatus::Protected;
3744 chat_id
3745 .set_protection_for_timestamp_sort(context, protect, timestamp, None)
3746 .await?;
3747 }
3748 Some(ProtectionStatus::Unprotected) => {
3749 chat_id.maybe_add_encrypted_msg(context, timestamp).await?;
3752 }
3753 None => {}
3754 }
3755
3756 if !context.get_config_bool(Config::Bot).await?
3757 && !context.get_config_bool(Config::SkipStartMessages).await?
3758 {
3759 let text = stock_str::new_group_send_first_message(context).await;
3760 add_info_msg(context, chat_id, &text, create_smeared_timestamp(context)).await?;
3761 }
3762
3763 Ok(chat_id)
3764}
3765
3766pub async fn create_broadcast(context: &Context, chat_name: String) -> Result<ChatId> {
3782 let grpid = create_id();
3783 create_broadcast_ex(context, Sync, grpid, chat_name).await
3784}
3785
3786pub(crate) async fn create_broadcast_ex(
3787 context: &Context,
3788 sync: sync::Sync,
3789 grpid: String,
3790 chat_name: String,
3791) -> Result<ChatId> {
3792 let row_id = {
3793 let chat_name = &chat_name;
3794 let grpid = &grpid;
3795 let trans_fn = |t: &mut rusqlite::Transaction| {
3796 let cnt = t.execute("UPDATE chats SET name=? WHERE grpid=?", (chat_name, grpid))?;
3797 ensure!(cnt <= 1, "{cnt} chats exist with grpid {grpid}");
3798 if cnt == 1 {
3799 return Ok(t.query_row(
3800 "SELECT id FROM chats WHERE grpid=? AND type=?",
3801 (grpid, Chattype::OutBroadcast),
3802 |row| {
3803 let id: isize = row.get(0)?;
3804 Ok(id)
3805 },
3806 )?);
3807 }
3808 t.execute(
3809 "INSERT INTO chats \
3810 (type, name, grpid, param, created_timestamp) \
3811 VALUES(?, ?, ?, \'U=1\', ?);",
3812 (
3813 Chattype::OutBroadcast,
3814 &chat_name,
3815 &grpid,
3816 create_smeared_timestamp(context),
3817 ),
3818 )?;
3819 Ok(t.last_insert_rowid().try_into()?)
3820 };
3821 context.sql.transaction(trans_fn).await?
3822 };
3823 let chat_id = ChatId::new(u32::try_from(row_id)?);
3824
3825 context.emit_msgs_changed_without_ids();
3826 chatlist_events::emit_chatlist_changed(context);
3827
3828 if sync.into() {
3829 let id = SyncId::Grpid(grpid);
3830 let action = SyncAction::CreateBroadcast(chat_name);
3831 self::sync(context, id, action).await.log_err(context).ok();
3832 }
3833
3834 Ok(chat_id)
3835}
3836
3837pub(crate) async fn update_chat_contacts_table(
3839 context: &Context,
3840 timestamp: i64,
3841 id: ChatId,
3842 contacts: &HashSet<ContactId>,
3843) -> Result<()> {
3844 context
3845 .sql
3846 .transaction(move |transaction| {
3847 transaction.execute(
3851 "UPDATE chats_contacts
3852 SET remove_timestamp=MAX(add_timestamp+1, ?)
3853 WHERE chat_id=?",
3854 (timestamp, id),
3855 )?;
3856
3857 if !contacts.is_empty() {
3858 let mut statement = transaction.prepare(
3859 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
3860 VALUES (?1, ?2, ?3)
3861 ON CONFLICT (chat_id, contact_id)
3862 DO UPDATE SET add_timestamp=remove_timestamp",
3863 )?;
3864
3865 for contact_id in contacts {
3866 statement.execute((id, contact_id, timestamp))?;
3870 }
3871 }
3872 Ok(())
3873 })
3874 .await?;
3875 Ok(())
3876}
3877
3878pub(crate) async fn add_to_chat_contacts_table(
3880 context: &Context,
3881 timestamp: i64,
3882 chat_id: ChatId,
3883 contact_ids: &[ContactId],
3884) -> Result<()> {
3885 context
3886 .sql
3887 .transaction(move |transaction| {
3888 let mut add_statement = transaction.prepare(
3889 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp) VALUES(?1, ?2, ?3)
3890 ON CONFLICT (chat_id, contact_id)
3891 DO UPDATE SET add_timestamp=MAX(remove_timestamp, ?3)",
3892 )?;
3893
3894 for contact_id in contact_ids {
3895 add_statement.execute((chat_id, contact_id, timestamp))?;
3896 }
3897 Ok(())
3898 })
3899 .await?;
3900
3901 Ok(())
3902}
3903
3904pub(crate) async fn remove_from_chat_contacts_table(
3907 context: &Context,
3908 chat_id: ChatId,
3909 contact_id: ContactId,
3910) -> Result<()> {
3911 let now = time();
3912 context
3913 .sql
3914 .execute(
3915 "UPDATE chats_contacts
3916 SET remove_timestamp=MAX(add_timestamp+1, ?)
3917 WHERE chat_id=? AND contact_id=?",
3918 (now, chat_id, contact_id),
3919 )
3920 .await?;
3921 Ok(())
3922}
3923
3924pub async fn add_contact_to_chat(
3927 context: &Context,
3928 chat_id: ChatId,
3929 contact_id: ContactId,
3930) -> Result<()> {
3931 add_contact_to_chat_ex(context, Sync, chat_id, contact_id, false).await?;
3932 Ok(())
3933}
3934
3935pub(crate) async fn add_contact_to_chat_ex(
3936 context: &Context,
3937 mut sync: sync::Sync,
3938 chat_id: ChatId,
3939 contact_id: ContactId,
3940 from_handshake: bool,
3941) -> Result<bool> {
3942 ensure!(!chat_id.is_special(), "can not add member to special chats");
3943 let contact = Contact::get_by_id(context, contact_id).await?;
3944 let mut msg = Message::new(Viewtype::default());
3945
3946 chat_id.reset_gossiped_timestamp(context).await?;
3947
3948 let mut chat = Chat::load_from_db(context, chat_id).await?;
3950 ensure!(
3951 chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast,
3952 "{} is not a group/broadcast where one can add members",
3953 chat_id
3954 );
3955 ensure!(
3956 Contact::real_exists_by_id(context, contact_id).await? || contact_id == ContactId::SELF,
3957 "invalid contact_id {} for adding to group",
3958 contact_id
3959 );
3960 ensure!(!chat.is_mailing_list(), "Mailing lists can't be changed");
3961 ensure!(
3962 chat.typ != Chattype::OutBroadcast || contact_id != ContactId::SELF,
3963 "Cannot add SELF to broadcast channel."
3964 );
3965 ensure!(
3966 chat.is_encrypted(context).await? == contact.is_key_contact(),
3967 "Only key-contacts can be added to encrypted chats"
3968 );
3969
3970 if !chat.is_self_in_chat(context).await? {
3971 context.emit_event(EventType::ErrorSelfNotInGroup(
3972 "Cannot add contact to group; self not in group.".into(),
3973 ));
3974 bail!("can not add contact because the account is not part of the group/broadcast");
3975 }
3976
3977 let sync_qr_code_tokens;
3978 if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
3979 chat.param
3980 .remove(Param::Unpromoted)
3981 .set_i64(Param::GroupNameTimestamp, smeared_time(context));
3982 chat.update_param(context).await?;
3983 sync_qr_code_tokens = true;
3984 } else {
3985 sync_qr_code_tokens = false;
3986 }
3987
3988 if context.is_self_addr(contact.get_addr()).await? {
3989 warn!(
3992 context,
3993 "Invalid attempt to add self e-mail address to group."
3994 );
3995 return Ok(false);
3996 }
3997
3998 if is_contact_in_chat(context, chat_id, contact_id).await? {
3999 if !from_handshake {
4000 return Ok(true);
4001 }
4002 } else {
4003 if chat.is_protected() && !contact.is_verified(context).await? {
4005 error!(
4006 context,
4007 "Cannot add non-bidirectionally verified contact {contact_id} to protected chat {chat_id}."
4008 );
4009 return Ok(false);
4010 }
4011 if is_contact_in_chat(context, chat_id, contact_id).await? {
4012 return Ok(false);
4013 }
4014 add_to_chat_contacts_table(context, time(), chat_id, &[contact_id]).await?;
4015 }
4016 if chat.typ == Chattype::Group && chat.is_promoted() {
4017 msg.viewtype = Viewtype::Text;
4018
4019 let contact_addr = contact.get_addr().to_lowercase();
4020 msg.text = stock_str::msg_add_member_local(context, contact.id, ContactId::SELF).await;
4021 msg.param.set_cmd(SystemMessage::MemberAddedToGroup);
4022 msg.param.set(Param::Arg, contact_addr);
4023 msg.param.set_int(Param::Arg2, from_handshake.into());
4024 msg.param
4025 .set_int(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
4026 send_msg(context, chat_id, &mut msg).await?;
4027
4028 sync = Nosync;
4029 if sync_qr_code_tokens
4035 && context
4036 .sync_qr_code_tokens(Some(chat.grpid.as_str()))
4037 .await
4038 .log_err(context)
4039 .is_ok()
4040 {
4041 context.scheduler.interrupt_inbox().await;
4042 }
4043 }
4044 context.emit_event(EventType::ChatModified(chat_id));
4045 if sync.into() {
4046 chat.sync_contacts(context).await.log_err(context).ok();
4047 }
4048 Ok(true)
4049}
4050
4051pub(crate) async fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result<bool> {
4057 let timestamp_some_days_ago = time() - DC_RESEND_USER_AVATAR_DAYS * 24 * 60 * 60;
4058 let needs_attach = context
4059 .sql
4060 .query_map(
4061 "SELECT c.selfavatar_sent
4062 FROM chats_contacts cc
4063 LEFT JOIN contacts c ON c.id=cc.contact_id
4064 WHERE cc.chat_id=? AND cc.contact_id!=? AND cc.add_timestamp >= cc.remove_timestamp",
4065 (chat_id, ContactId::SELF),
4066 |row| Ok(row.get::<_, i64>(0)),
4067 |rows| {
4068 let mut needs_attach = false;
4069 for row in rows {
4070 let row = row?;
4071 let selfavatar_sent = row?;
4072 if selfavatar_sent < timestamp_some_days_ago {
4073 needs_attach = true;
4074 }
4075 }
4076 Ok(needs_attach)
4077 },
4078 )
4079 .await?;
4080 Ok(needs_attach)
4081}
4082
4083#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
4085pub enum MuteDuration {
4086 NotMuted,
4088
4089 Forever,
4091
4092 Until(std::time::SystemTime),
4094}
4095
4096impl rusqlite::types::ToSql for MuteDuration {
4097 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
4098 let duration: i64 = match &self {
4099 MuteDuration::NotMuted => 0,
4100 MuteDuration::Forever => -1,
4101 MuteDuration::Until(when) => {
4102 let duration = when
4103 .duration_since(SystemTime::UNIX_EPOCH)
4104 .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
4105 i64::try_from(duration.as_secs())
4106 .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?
4107 }
4108 };
4109 let val = rusqlite::types::Value::Integer(duration);
4110 let out = rusqlite::types::ToSqlOutput::Owned(val);
4111 Ok(out)
4112 }
4113}
4114
4115impl rusqlite::types::FromSql for MuteDuration {
4116 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
4117 match i64::column_result(value)? {
4120 0 => Ok(MuteDuration::NotMuted),
4121 -1 => Ok(MuteDuration::Forever),
4122 n if n > 0 => match SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(n as u64)) {
4123 Some(t) => Ok(MuteDuration::Until(t)),
4124 None => Err(rusqlite::types::FromSqlError::OutOfRange(n)),
4125 },
4126 _ => Ok(MuteDuration::NotMuted),
4127 }
4128 }
4129}
4130
4131pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<()> {
4133 set_muted_ex(context, Sync, chat_id, duration).await
4134}
4135
4136pub(crate) async fn set_muted_ex(
4137 context: &Context,
4138 sync: sync::Sync,
4139 chat_id: ChatId,
4140 duration: MuteDuration,
4141) -> Result<()> {
4142 ensure!(!chat_id.is_special(), "Invalid chat ID");
4143 context
4144 .sql
4145 .execute(
4146 "UPDATE chats SET muted_until=? WHERE id=?;",
4147 (duration, chat_id),
4148 )
4149 .await
4150 .context(format!("Failed to set mute duration for {chat_id}"))?;
4151 context.emit_event(EventType::ChatModified(chat_id));
4152 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4153 if sync.into() {
4154 let chat = Chat::load_from_db(context, chat_id).await?;
4155 chat.sync(context, SyncAction::SetMuted(duration))
4156 .await
4157 .log_err(context)
4158 .ok();
4159 }
4160 Ok(())
4161}
4162
4163pub async fn remove_contact_from_chat(
4165 context: &Context,
4166 chat_id: ChatId,
4167 contact_id: ContactId,
4168) -> Result<()> {
4169 ensure!(
4170 !chat_id.is_special(),
4171 "bad chat_id, can not be special chat: {}",
4172 chat_id
4173 );
4174 ensure!(
4175 !contact_id.is_special() || contact_id == ContactId::SELF,
4176 "Cannot remove special contact"
4177 );
4178
4179 let chat = Chat::load_from_db(context, chat_id).await?;
4180 if chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast {
4181 if !chat.is_self_in_chat(context).await? {
4182 let err_msg = format!(
4183 "Cannot remove contact {contact_id} from chat {chat_id}: self not in group."
4184 );
4185 context.emit_event(EventType::ErrorSelfNotInGroup(err_msg.clone()));
4186 bail!("{}", err_msg);
4187 } else {
4188 let mut sync = Nosync;
4189
4190 if chat.is_promoted() {
4191 remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
4192 } else {
4193 context
4194 .sql
4195 .execute(
4196 "DELETE FROM chats_contacts
4197 WHERE chat_id=? AND contact_id=?",
4198 (chat_id, contact_id),
4199 )
4200 .await?;
4201 }
4202
4203 if let Some(contact) = Contact::get_by_id_optional(context, contact_id).await? {
4207 if chat.typ == Chattype::Group && chat.is_promoted() {
4208 let addr = contact.get_addr();
4209
4210 let res = send_member_removal_msg(context, chat_id, contact_id, addr).await;
4211
4212 if contact_id == ContactId::SELF {
4213 res?;
4214 set_group_explicitly_left(context, &chat.grpid).await?;
4215 } else if let Err(e) = res {
4216 warn!(
4217 context,
4218 "remove_contact_from_chat({chat_id}, {contact_id}): send_msg() failed: {e:#}."
4219 );
4220 }
4221 } else {
4222 sync = Sync;
4223 }
4224 }
4225 context.emit_event(EventType::ChatModified(chat_id));
4226 if sync.into() {
4227 chat.sync_contacts(context).await.log_err(context).ok();
4228 }
4229 }
4230 } else if chat.typ == Chattype::InBroadcast && contact_id == ContactId::SELF {
4231 let self_addr = context.get_primary_self_addr().await?;
4234 send_member_removal_msg(context, chat_id, contact_id, &self_addr).await?;
4235 } else {
4236 bail!("Cannot remove members from non-group chats.");
4237 }
4238
4239 Ok(())
4240}
4241
4242async fn send_member_removal_msg(
4243 context: &Context,
4244 chat_id: ChatId,
4245 contact_id: ContactId,
4246 addr: &str,
4247) -> Result<MsgId> {
4248 let mut msg = Message::new(Viewtype::Text);
4249
4250 if contact_id == ContactId::SELF {
4251 msg.text = stock_str::msg_group_left_local(context, ContactId::SELF).await;
4252 } else {
4253 msg.text = stock_str::msg_del_member_local(context, contact_id, ContactId::SELF).await;
4254 }
4255
4256 msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup);
4257 msg.param.set(Param::Arg, addr.to_lowercase());
4258 msg.param
4259 .set(Param::ContactAddedRemoved, contact_id.to_u32());
4260
4261 send_msg(context, chat_id, &mut msg).await
4262}
4263
4264async fn set_group_explicitly_left(context: &Context, grpid: &str) -> Result<()> {
4265 if !is_group_explicitly_left(context, grpid).await? {
4266 context
4267 .sql
4268 .execute("INSERT INTO leftgrps (grpid) VALUES(?);", (grpid,))
4269 .await?;
4270 }
4271
4272 Ok(())
4273}
4274
4275pub(crate) async fn is_group_explicitly_left(context: &Context, grpid: &str) -> Result<bool> {
4276 let exists = context
4277 .sql
4278 .exists("SELECT COUNT(*) FROM leftgrps WHERE grpid=?;", (grpid,))
4279 .await?;
4280 Ok(exists)
4281}
4282
4283pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -> Result<()> {
4285 rename_ex(context, Sync, chat_id, new_name).await
4286}
4287
4288async fn rename_ex(
4289 context: &Context,
4290 mut sync: sync::Sync,
4291 chat_id: ChatId,
4292 new_name: &str,
4293) -> Result<()> {
4294 let new_name = sanitize_single_line(new_name);
4295 let mut success = false;
4297
4298 ensure!(!new_name.is_empty(), "Invalid name");
4299 ensure!(!chat_id.is_special(), "Invalid chat ID");
4300
4301 let chat = Chat::load_from_db(context, chat_id).await?;
4302 let mut msg = Message::new(Viewtype::default());
4303
4304 if chat.typ == Chattype::Group
4305 || chat.typ == Chattype::Mailinglist
4306 || chat.typ == Chattype::OutBroadcast
4307 {
4308 if chat.name == new_name {
4309 success = true;
4310 } else if !chat.is_self_in_chat(context).await? {
4311 context.emit_event(EventType::ErrorSelfNotInGroup(
4312 "Cannot set chat name; self not in group".into(),
4313 ));
4314 } else {
4315 context
4316 .sql
4317 .execute(
4318 "UPDATE chats SET name=? WHERE id=?;",
4319 (new_name.to_string(), chat_id),
4320 )
4321 .await?;
4322 if chat.is_promoted()
4323 && !chat.is_mailing_list()
4324 && sanitize_single_line(&chat.name) != new_name
4325 {
4326 msg.viewtype = Viewtype::Text;
4327 msg.text =
4328 stock_str::msg_grp_name(context, &chat.name, &new_name, ContactId::SELF).await;
4329 msg.param.set_cmd(SystemMessage::GroupNameChanged);
4330 if !chat.name.is_empty() {
4331 msg.param.set(Param::Arg, &chat.name);
4332 }
4333 msg.id = send_msg(context, chat_id, &mut msg).await?;
4334 context.emit_msgs_changed(chat_id, msg.id);
4335 sync = Nosync;
4336 }
4337 context.emit_event(EventType::ChatModified(chat_id));
4338 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4339 success = true;
4340 }
4341 }
4342
4343 if !success {
4344 bail!("Failed to set name");
4345 }
4346 if sync.into() && chat.name != new_name {
4347 let sync_name = new_name.to_string();
4348 chat.sync(context, SyncAction::Rename(sync_name))
4349 .await
4350 .log_err(context)
4351 .ok();
4352 }
4353 Ok(())
4354}
4355
4356pub async fn set_chat_profile_image(
4362 context: &Context,
4363 chat_id: ChatId,
4364 new_image: &str, ) -> Result<()> {
4366 ensure!(!chat_id.is_special(), "Invalid chat ID");
4367 let mut chat = Chat::load_from_db(context, chat_id).await?;
4368 ensure!(
4369 chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast,
4370 "Can only set profile image for groups / broadcasts"
4371 );
4372 ensure!(
4373 !chat.grpid.is_empty(),
4374 "Cannot set profile image for ad hoc groups"
4375 );
4376 if !chat.is_self_in_chat(context).await? {
4378 context.emit_event(EventType::ErrorSelfNotInGroup(
4379 "Cannot set chat profile image; self not in group.".into(),
4380 ));
4381 bail!("Failed to set profile image");
4382 }
4383 let mut msg = Message::new(Viewtype::Text);
4384 msg.param
4385 .set_int(Param::Cmd, SystemMessage::GroupImageChanged as i32);
4386 if new_image.is_empty() {
4387 chat.param.remove(Param::ProfileImage);
4388 msg.param.remove(Param::Arg);
4389 msg.text = stock_str::msg_grp_img_deleted(context, ContactId::SELF).await;
4390 } else {
4391 let mut image_blob = BlobObject::create_and_deduplicate(
4392 context,
4393 Path::new(new_image),
4394 Path::new(new_image),
4395 )?;
4396 image_blob.recode_to_avatar_size(context).await?;
4397 chat.param.set(Param::ProfileImage, image_blob.as_name());
4398 msg.param.set(Param::Arg, image_blob.as_name());
4399 msg.text = stock_str::msg_grp_img_changed(context, ContactId::SELF).await;
4400 }
4401 chat.update_param(context).await?;
4402 if chat.is_promoted() && !chat.is_mailing_list() {
4403 msg.id = send_msg(context, chat_id, &mut msg).await?;
4404 context.emit_msgs_changed(chat_id, msg.id);
4405 }
4406 context.emit_event(EventType::ChatModified(chat_id));
4407 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4408 Ok(())
4409}
4410
4411pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Result<()> {
4413 ensure!(!msg_ids.is_empty(), "empty msgs_ids: nothing to forward");
4414 ensure!(!chat_id.is_special(), "can not forward to special chat");
4415
4416 let mut created_msgs: Vec<MsgId> = Vec::new();
4417 let mut curr_timestamp: i64;
4418
4419 chat_id
4420 .unarchive_if_not_muted(context, MessageState::Undefined)
4421 .await?;
4422 let mut chat = Chat::load_from_db(context, chat_id).await?;
4423 if let Some(reason) = chat.why_cant_send(context).await? {
4424 bail!("cannot send to {}: {}", chat_id, reason);
4425 }
4426 curr_timestamp = create_smeared_timestamps(context, msg_ids.len());
4427 let mut msgs = Vec::with_capacity(msg_ids.len());
4428 for id in msg_ids {
4429 let ts: i64 = context
4430 .sql
4431 .query_get_value("SELECT timestamp FROM msgs WHERE id=?", (id,))
4432 .await?
4433 .with_context(|| format!("No message {id}"))?;
4434 msgs.push((ts, *id));
4435 }
4436 msgs.sort_unstable();
4437 for (_, id) in msgs {
4438 let src_msg_id: MsgId = id;
4439 let mut msg = Message::load_from_db(context, src_msg_id).await?;
4440 if msg.state == MessageState::OutDraft {
4441 bail!("cannot forward drafts.");
4442 }
4443
4444 if msg.get_viewtype() != Viewtype::Sticker {
4445 msg.param
4446 .set_int(Param::Forwarded, src_msg_id.to_u32() as i32);
4447 }
4448
4449 msg.param.remove(Param::GuaranteeE2ee);
4450 msg.param.remove(Param::ForcePlaintext);
4451 msg.param.remove(Param::Cmd);
4452 msg.param.remove(Param::OverrideSenderDisplayname);
4453 msg.param.remove(Param::WebxdcDocument);
4454 msg.param.remove(Param::WebxdcDocumentTimestamp);
4455 msg.param.remove(Param::WebxdcSummary);
4456 msg.param.remove(Param::WebxdcSummaryTimestamp);
4457 msg.param.remove(Param::IsEdited);
4458 msg.in_reply_to = None;
4459
4460 msg.subject = "".to_string();
4462
4463 msg.state = MessageState::OutPending;
4464 msg.rfc724_mid = create_outgoing_rfc724_mid();
4465 msg.timestamp_sort = curr_timestamp;
4466 chat.prepare_msg_raw(context, &mut msg, None).await?;
4467
4468 curr_timestamp += 1;
4469 if !create_send_msg_jobs(context, &mut msg).await?.is_empty() {
4470 context.scheduler.interrupt_smtp().await;
4471 }
4472 created_msgs.push(msg.id);
4473 }
4474 for msg_id in created_msgs {
4475 context.emit_msgs_changed(chat_id, msg_id);
4476 }
4477 Ok(())
4478}
4479
4480pub async fn save_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
4483 let mut msgs = Vec::with_capacity(msg_ids.len());
4484 for id in msg_ids {
4485 let ts: i64 = context
4486 .sql
4487 .query_get_value("SELECT timestamp FROM msgs WHERE id=?", (id,))
4488 .await?
4489 .with_context(|| format!("No message {id}"))?;
4490 msgs.push((ts, *id));
4491 }
4492 msgs.sort_unstable();
4493 for (_, src_msg_id) in msgs {
4494 let dest_rfc724_mid = create_outgoing_rfc724_mid();
4495 let src_rfc724_mid = save_copy_in_self_talk(context, src_msg_id, &dest_rfc724_mid).await?;
4496 context
4497 .add_sync_item(SyncData::SaveMessage {
4498 src: src_rfc724_mid,
4499 dest: dest_rfc724_mid,
4500 })
4501 .await?;
4502 }
4503 context.scheduler.interrupt_inbox().await;
4504 Ok(())
4505}
4506
4507pub(crate) async fn save_copy_in_self_talk(
4513 context: &Context,
4514 src_msg_id: MsgId,
4515 dest_rfc724_mid: &String,
4516) -> Result<String> {
4517 let dest_chat_id = ChatId::create_for_contact(context, ContactId::SELF).await?;
4518 let mut msg = Message::load_from_db(context, src_msg_id).await?;
4519 msg.param.remove(Param::Cmd);
4520 msg.param.remove(Param::WebxdcDocument);
4521 msg.param.remove(Param::WebxdcDocumentTimestamp);
4522 msg.param.remove(Param::WebxdcSummary);
4523 msg.param.remove(Param::WebxdcSummaryTimestamp);
4524
4525 if !msg.original_msg_id.is_unset() {
4526 bail!("message already saved.");
4527 }
4528
4529 let copy_fields = "from_id, to_id, timestamp_rcvd, type, txt,
4530 mime_modified, mime_headers, mime_compressed, mime_in_reply_to, subject, msgrmsg";
4531 let row_id = context
4532 .sql
4533 .insert(
4534 &format!(
4535 "INSERT INTO msgs ({copy_fields},
4536 timestamp_sent,
4537 chat_id, rfc724_mid, state, timestamp, param, starred)
4538 SELECT {copy_fields},
4539 -- Outgoing messages on originating device
4540 -- have timestamp_sent == 0.
4541 -- We copy sort timestamp instead
4542 -- so UIs display the same timestamp
4543 -- for saved and original message.
4544 IIF(timestamp_sent == 0, timestamp, timestamp_sent),
4545 ?, ?, ?, ?, ?, ?
4546 FROM msgs WHERE id=?;"
4547 ),
4548 (
4549 dest_chat_id,
4550 dest_rfc724_mid,
4551 if msg.from_id == ContactId::SELF {
4552 MessageState::OutDelivered
4553 } else {
4554 MessageState::InSeen
4555 },
4556 create_smeared_timestamp(context),
4557 msg.param.to_string(),
4558 src_msg_id,
4559 src_msg_id,
4560 ),
4561 )
4562 .await?;
4563 let dest_msg_id = MsgId::new(row_id.try_into()?);
4564
4565 context.emit_msgs_changed(msg.chat_id, src_msg_id);
4566 context.emit_msgs_changed(dest_chat_id, dest_msg_id);
4567 chatlist_events::emit_chatlist_changed(context);
4568 chatlist_events::emit_chatlist_item_changed(context, dest_chat_id);
4569
4570 Ok(msg.rfc724_mid)
4571}
4572
4573pub async fn resend_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
4577 let mut msgs: Vec<Message> = Vec::new();
4578 for msg_id in msg_ids {
4579 let msg = Message::load_from_db(context, *msg_id).await?;
4580 ensure!(
4581 msg.from_id == ContactId::SELF,
4582 "can resend only own messages"
4583 );
4584 ensure!(!msg.is_info(), "cannot resend info messages");
4585 msgs.push(msg)
4586 }
4587
4588 for mut msg in msgs {
4589 match msg.get_state() {
4590 MessageState::OutPending
4592 | MessageState::OutFailed
4593 | MessageState::OutDelivered
4594 | MessageState::OutMdnRcvd => {
4595 message::update_msg_state(context, msg.id, MessageState::OutPending).await?
4596 }
4597 msg_state => bail!("Unexpected message state {msg_state}"),
4598 }
4599 msg.timestamp_sort = create_smeared_timestamp(context);
4600 if create_send_msg_jobs(context, &mut msg).await?.is_empty() {
4601 continue;
4602 }
4603
4604 context.emit_event(EventType::MsgsChanged {
4608 chat_id: msg.chat_id,
4609 msg_id: msg.id,
4610 });
4611 chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
4613
4614 if msg.viewtype == Viewtype::Webxdc {
4615 let conn_fn = |conn: &mut rusqlite::Connection| {
4616 let range = conn.query_row(
4617 "SELECT IFNULL(min(id), 1), IFNULL(max(id), 0) \
4618 FROM msgs_status_updates WHERE msg_id=?",
4619 (msg.id,),
4620 |row| {
4621 let min_id: StatusUpdateSerial = row.get(0)?;
4622 let max_id: StatusUpdateSerial = row.get(1)?;
4623 Ok((min_id, max_id))
4624 },
4625 )?;
4626 if range.0 > range.1 {
4627 return Ok(());
4628 };
4629 conn.execute(
4633 "INSERT INTO smtp_status_updates (msg_id, first_serial, last_serial, descr) \
4634 VALUES(?, ?, ?, '') \
4635 ON CONFLICT(msg_id) \
4636 DO UPDATE SET first_serial=min(first_serial - 1, excluded.first_serial)",
4637 (msg.id, range.0, range.1),
4638 )?;
4639 Ok(())
4640 };
4641 context.sql.call_write(conn_fn).await?;
4642 }
4643 context.scheduler.interrupt_smtp().await;
4644 }
4645 Ok(())
4646}
4647
4648pub(crate) async fn get_chat_cnt(context: &Context) -> Result<usize> {
4649 if context.sql.is_open().await {
4650 let count = context
4652 .sql
4653 .count("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;", ())
4654 .await?;
4655 Ok(count)
4656 } else {
4657 Ok(0)
4658 }
4659}
4660
4661pub(crate) async fn get_chat_id_by_grpid(
4663 context: &Context,
4664 grpid: &str,
4665) -> Result<Option<(ChatId, bool, Blocked)>> {
4666 context
4667 .sql
4668 .query_row_optional(
4669 "SELECT id, blocked, protected FROM chats WHERE grpid=?;",
4670 (grpid,),
4671 |row| {
4672 let chat_id = row.get::<_, ChatId>(0)?;
4673
4674 let b = row.get::<_, Option<Blocked>>(1)?.unwrap_or_default();
4675 let p = row
4676 .get::<_, Option<ProtectionStatus>>(2)?
4677 .unwrap_or_default();
4678 Ok((chat_id, p == ProtectionStatus::Protected, b))
4679 },
4680 )
4681 .await
4682}
4683
4684pub async fn add_device_msg_with_importance(
4689 context: &Context,
4690 label: Option<&str>,
4691 msg: Option<&mut Message>,
4692 important: bool,
4693) -> Result<MsgId> {
4694 ensure!(
4695 label.is_some() || msg.is_some(),
4696 "device-messages need label, msg or both"
4697 );
4698 let mut chat_id = ChatId::new(0);
4699 let mut msg_id = MsgId::new_unset();
4700
4701 if let Some(label) = label {
4702 if was_device_msg_ever_added(context, label).await? {
4703 info!(context, "Device-message {label} already added.");
4704 return Ok(msg_id);
4705 }
4706 }
4707
4708 if let Some(msg) = msg {
4709 chat_id = ChatId::get_for_contact(context, ContactId::DEVICE).await?;
4710
4711 let rfc724_mid = create_outgoing_rfc724_mid();
4712 let timestamp_sent = create_smeared_timestamp(context);
4713
4714 msg.timestamp_sort = timestamp_sent;
4717 if let Some(last_msg_time) = chat_id.get_timestamp(context).await? {
4718 if msg.timestamp_sort <= last_msg_time {
4719 msg.timestamp_sort = last_msg_time + 1;
4720 }
4721 }
4722 prepare_msg_blob(context, msg).await?;
4723 let state = MessageState::InFresh;
4724 let row_id = context
4725 .sql
4726 .insert(
4727 "INSERT INTO msgs (
4728 chat_id,
4729 from_id,
4730 to_id,
4731 timestamp,
4732 timestamp_sent,
4733 timestamp_rcvd,
4734 type,state,
4735 txt,
4736 txt_normalized,
4737 param,
4738 rfc724_mid)
4739 VALUES (?,?,?,?,?,?,?,?,?,?,?,?);",
4740 (
4741 chat_id,
4742 ContactId::DEVICE,
4743 ContactId::SELF,
4744 msg.timestamp_sort,
4745 timestamp_sent,
4746 timestamp_sent, msg.viewtype,
4748 state,
4749 &msg.text,
4750 message::normalize_text(&msg.text),
4751 msg.param.to_string(),
4752 rfc724_mid,
4753 ),
4754 )
4755 .await?;
4756 context.new_msgs_notify.notify_one();
4757
4758 msg_id = MsgId::new(u32::try_from(row_id)?);
4759 if !msg.hidden {
4760 chat_id.unarchive_if_not_muted(context, state).await?;
4761 }
4762 }
4763
4764 if let Some(label) = label {
4765 context
4766 .sql
4767 .execute("INSERT INTO devmsglabels (label) VALUES (?);", (label,))
4768 .await?;
4769 }
4770
4771 if !msg_id.is_unset() {
4772 chat_id.emit_msg_event(context, msg_id, important);
4773 }
4774
4775 Ok(msg_id)
4776}
4777
4778pub async fn add_device_msg(
4780 context: &Context,
4781 label: Option<&str>,
4782 msg: Option<&mut Message>,
4783) -> Result<MsgId> {
4784 add_device_msg_with_importance(context, label, msg, false).await
4785}
4786
4787pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result<bool> {
4789 ensure!(!label.is_empty(), "empty label");
4790 let exists = context
4791 .sql
4792 .exists(
4793 "SELECT COUNT(label) FROM devmsglabels WHERE label=?",
4794 (label,),
4795 )
4796 .await?;
4797
4798 Ok(exists)
4799}
4800
4801pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Result<()> {
4809 context
4810 .sql
4811 .execute("DELETE FROM msgs WHERE from_id=?;", (ContactId::DEVICE,))
4812 .await?;
4813 context.sql.execute("DELETE FROM devmsglabels;", ()).await?;
4814
4815 context
4817 .sql
4818 .execute(
4819 r#"INSERT INTO devmsglabels (label) VALUES ("core-welcome-image"), ("core-welcome")"#,
4820 (),
4821 )
4822 .await?;
4823 context
4824 .set_config_internal(Config::QuotaExceeding, None)
4825 .await?;
4826 Ok(())
4827}
4828
4829#[expect(clippy::too_many_arguments)]
4834pub(crate) async fn add_info_msg_with_cmd(
4835 context: &Context,
4836 chat_id: ChatId,
4837 text: &str,
4838 cmd: SystemMessage,
4839 timestamp_sort: i64,
4840 timestamp_sent_rcvd: Option<i64>,
4842 parent: Option<&Message>,
4843 from_id: Option<ContactId>,
4844 added_removed_id: Option<ContactId>,
4845) -> Result<MsgId> {
4846 let rfc724_mid = create_outgoing_rfc724_mid();
4847 let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?;
4848
4849 let mut param = Params::new();
4850 if cmd != SystemMessage::Unknown {
4851 param.set_cmd(cmd);
4852 }
4853 if let Some(contact_id) = added_removed_id {
4854 param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
4855 }
4856
4857 let row_id =
4858 context.sql.insert(
4859 "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)
4860 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?);",
4861 (
4862 chat_id,
4863 from_id.unwrap_or(ContactId::INFO),
4864 ContactId::INFO,
4865 timestamp_sort,
4866 timestamp_sent_rcvd.unwrap_or(0),
4867 timestamp_sent_rcvd.unwrap_or(0),
4868 Viewtype::Text,
4869 MessageState::InNoticed,
4870 text,
4871 message::normalize_text(text),
4872 rfc724_mid,
4873 ephemeral_timer,
4874 param.to_string(),
4875 parent.map(|msg|msg.rfc724_mid.clone()).unwrap_or_default()
4876 )
4877 ).await?;
4878 context.new_msgs_notify.notify_one();
4879
4880 let msg_id = MsgId::new(row_id.try_into()?);
4881 context.emit_msgs_changed(chat_id, msg_id);
4882
4883 Ok(msg_id)
4884}
4885
4886pub(crate) async fn add_info_msg(
4888 context: &Context,
4889 chat_id: ChatId,
4890 text: &str,
4891 timestamp: i64,
4892) -> Result<MsgId> {
4893 add_info_msg_with_cmd(
4894 context,
4895 chat_id,
4896 text,
4897 SystemMessage::Unknown,
4898 timestamp,
4899 None,
4900 None,
4901 None,
4902 None,
4903 )
4904 .await
4905}
4906
4907pub(crate) async fn update_msg_text_and_timestamp(
4908 context: &Context,
4909 chat_id: ChatId,
4910 msg_id: MsgId,
4911 text: &str,
4912 timestamp: i64,
4913) -> Result<()> {
4914 context
4915 .sql
4916 .execute(
4917 "UPDATE msgs SET txt=?, txt_normalized=?, timestamp=? WHERE id=?;",
4918 (text, message::normalize_text(text), timestamp, msg_id),
4919 )
4920 .await?;
4921 context.emit_msgs_changed(chat_id, msg_id);
4922 Ok(())
4923}
4924
4925async fn set_contacts_by_addrs(context: &Context, id: ChatId, addrs: &[String]) -> Result<()> {
4927 let chat = Chat::load_from_db(context, id).await?;
4928 ensure!(
4929 !chat.is_encrypted(context).await?,
4930 "Cannot add address-contacts to encrypted chat {id}"
4931 );
4932 ensure!(
4933 chat.typ == Chattype::OutBroadcast,
4934 "{id} is not a broadcast list",
4935 );
4936 let mut contacts = HashSet::new();
4937 for addr in addrs {
4938 let contact_addr = ContactAddress::new(addr)?;
4939 let contact = Contact::add_or_lookup(context, "", &contact_addr, Origin::Hidden)
4940 .await?
4941 .0;
4942 contacts.insert(contact);
4943 }
4944 let contacts_old = HashSet::<ContactId>::from_iter(get_chat_contacts(context, id).await?);
4945 if contacts == contacts_old {
4946 return Ok(());
4947 }
4948 context
4949 .sql
4950 .transaction(move |transaction| {
4951 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (id,))?;
4952
4953 let mut statement = transaction
4956 .prepare("INSERT INTO chats_contacts (chat_id, contact_id) VALUES (?, ?)")?;
4957 for contact_id in &contacts {
4958 statement.execute((id, contact_id))?;
4959 }
4960 Ok(())
4961 })
4962 .await?;
4963 context.emit_event(EventType::ChatModified(id));
4964 Ok(())
4965}
4966
4967async fn set_contacts_by_fingerprints(
4971 context: &Context,
4972 id: ChatId,
4973 fingerprint_addrs: &[(String, String)],
4974) -> Result<()> {
4975 let chat = Chat::load_from_db(context, id).await?;
4976 ensure!(
4977 chat.is_encrypted(context).await?,
4978 "Cannot add key-contacts to unencrypted chat {id}"
4979 );
4980 ensure!(
4981 chat.typ == Chattype::OutBroadcast,
4982 "{id} is not a broadcast list",
4983 );
4984 let mut contacts = HashSet::new();
4985 for (fingerprint, addr) in fingerprint_addrs {
4986 let contact_addr = ContactAddress::new(addr)?;
4987 let contact =
4988 Contact::add_or_lookup_ex(context, "", &contact_addr, fingerprint, Origin::Hidden)
4989 .await?
4990 .0;
4991 contacts.insert(contact);
4992 }
4993 let contacts_old = HashSet::<ContactId>::from_iter(get_chat_contacts(context, id).await?);
4994 if contacts == contacts_old {
4995 return Ok(());
4996 }
4997 context
4998 .sql
4999 .transaction(move |transaction| {
5000 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (id,))?;
5001
5002 let mut statement = transaction
5005 .prepare("INSERT INTO chats_contacts (chat_id, contact_id) VALUES (?, ?)")?;
5006 for contact_id in &contacts {
5007 statement.execute((id, contact_id))?;
5008 }
5009 Ok(())
5010 })
5011 .await?;
5012 context.emit_event(EventType::ChatModified(id));
5013 Ok(())
5014}
5015
5016#[derive(Debug, Serialize, Deserialize, PartialEq)]
5018pub(crate) enum SyncId {
5019 ContactAddr(String),
5021
5022 ContactFingerprint(String),
5024
5025 Grpid(String),
5026 Msgids(Vec<String>),
5028
5029 Device,
5031}
5032
5033#[derive(Debug, Serialize, Deserialize, PartialEq)]
5035pub(crate) enum SyncAction {
5036 Block,
5037 Unblock,
5038 Accept,
5039 SetVisibility(ChatVisibility),
5040 SetMuted(MuteDuration),
5041 CreateBroadcast(String),
5043 Rename(String),
5044 SetContacts(Vec<String>),
5046 SetPgpContacts(Vec<(String, String)>),
5050 Delete,
5051}
5052
5053impl Context {
5054 pub(crate) async fn sync_alter_chat(&self, id: &SyncId, action: &SyncAction) -> Result<()> {
5056 let chat_id = match id {
5057 SyncId::ContactAddr(addr) => {
5058 if let SyncAction::Rename(to) = action {
5059 Contact::create_ex(self, Nosync, to, addr).await?;
5060 return Ok(());
5061 }
5062 let addr = ContactAddress::new(addr).context("Invalid address")?;
5063 let (contact_id, _) =
5064 Contact::add_or_lookup(self, "", &addr, Origin::Hidden).await?;
5065 match action {
5066 SyncAction::Block => {
5067 return contact::set_blocked(self, Nosync, contact_id, true).await;
5068 }
5069 SyncAction::Unblock => {
5070 return contact::set_blocked(self, Nosync, contact_id, false).await;
5071 }
5072 _ => (),
5073 }
5074 ChatIdBlocked::get_for_contact(self, contact_id, Blocked::Request)
5077 .await?
5078 .id
5079 }
5080 SyncId::ContactFingerprint(fingerprint) => {
5081 let name = "";
5082 let addr = "";
5083 let (contact_id, _) =
5084 Contact::add_or_lookup_ex(self, name, addr, fingerprint, Origin::Hidden)
5085 .await?;
5086 match action {
5087 SyncAction::Rename(to) => {
5088 contact_id.set_name_ex(self, Nosync, to).await?;
5089 self.emit_event(EventType::ContactsChanged(Some(contact_id)));
5090 return Ok(());
5091 }
5092 SyncAction::Block => {
5093 return contact::set_blocked(self, Nosync, contact_id, true).await;
5094 }
5095 SyncAction::Unblock => {
5096 return contact::set_blocked(self, Nosync, contact_id, false).await;
5097 }
5098 _ => (),
5099 }
5100 ChatIdBlocked::get_for_contact(self, contact_id, Blocked::Request)
5101 .await?
5102 .id
5103 }
5104 SyncId::Grpid(grpid) => {
5105 if let SyncAction::CreateBroadcast(name) = action {
5106 create_broadcast_ex(self, Nosync, grpid.clone(), name.clone()).await?;
5107 return Ok(());
5108 }
5109 get_chat_id_by_grpid(self, grpid)
5110 .await?
5111 .with_context(|| format!("No chat for grpid '{grpid}'"))?
5112 .0
5113 }
5114 SyncId::Msgids(msgids) => {
5115 let msg = message::get_by_rfc724_mids(self, msgids)
5116 .await?
5117 .with_context(|| format!("No message found for Message-IDs {msgids:?}"))?;
5118 ChatId::lookup_by_message(&msg)
5119 .with_context(|| format!("No chat found for Message-IDs {msgids:?}"))?
5120 }
5121 SyncId::Device => ChatId::get_for_contact(self, ContactId::DEVICE).await?,
5122 };
5123 match action {
5124 SyncAction::Block => chat_id.block_ex(self, Nosync).await,
5125 SyncAction::Unblock => chat_id.unblock_ex(self, Nosync).await,
5126 SyncAction::Accept => chat_id.accept_ex(self, Nosync).await,
5127 SyncAction::SetVisibility(v) => chat_id.set_visibility_ex(self, Nosync, *v).await,
5128 SyncAction::SetMuted(duration) => set_muted_ex(self, Nosync, chat_id, *duration).await,
5129 SyncAction::CreateBroadcast(_) => {
5130 Err(anyhow!("sync_alter_chat({id:?}, {action:?}): Bad request."))
5131 }
5132 SyncAction::Rename(to) => rename_ex(self, Nosync, chat_id, to).await,
5133 SyncAction::SetContacts(addrs) => set_contacts_by_addrs(self, chat_id, addrs).await,
5134 SyncAction::SetPgpContacts(fingerprint_addrs) => {
5135 set_contacts_by_fingerprints(self, chat_id, fingerprint_addrs).await
5136 }
5137 SyncAction::Delete => chat_id.delete_ex(self, Nosync).await,
5138 }
5139 }
5140
5141 pub(crate) fn on_archived_chats_maybe_noticed(&self) {
5146 self.emit_msgs_changed_without_msg_id(DC_CHAT_ID_ARCHIVED_LINK);
5147 }
5148}
5149
5150#[cfg(test)]
5151mod chat_tests;