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 mail_builder::mime::MimePart;
16use serde::{Deserialize, Serialize};
17use strum_macros::EnumIter;
18
19use crate::blob::BlobObject;
20use crate::chatlist::Chatlist;
21use crate::color::str_to_color;
22use crate::config::Config;
23use crate::constants::{
24 Blocked, Chattype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK, DC_CHAT_ID_LAST_SPECIAL,
25 DC_CHAT_ID_TRASH, DC_RESEND_USER_AVATAR_DAYS, EDITED_PREFIX, TIMESTAMP_SENT_TOLERANCE,
26};
27use crate::contact::{self, Contact, ContactId, Origin};
28use crate::context::Context;
29use crate::debug_logging::maybe_set_logging_xdc;
30use crate::download::DownloadState;
31use crate::ephemeral::{Timer as EphemeralTimer, start_chat_ephemeral_timers};
32use crate::events::EventType;
33use crate::key::self_fingerprint;
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(Debug, Clone, Copy, PartialEq, Eq)]
74pub(crate) enum CantSendReason {
75 SpecialChat,
77
78 DeviceChat,
80
81 ContactRequest,
83
84 ReadOnlyMailingList,
86
87 InBroadcast,
89
90 NotAMember,
92
93 MissingKey,
95}
96
97impl fmt::Display for CantSendReason {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match self {
100 Self::SpecialChat => write!(f, "the chat is a special chat"),
101 Self::DeviceChat => write!(f, "the chat is a device chat"),
102 Self::ContactRequest => write!(
103 f,
104 "contact request chat should be accepted before sending messages"
105 ),
106 Self::ReadOnlyMailingList => {
107 write!(f, "mailing list does not have a know post address")
108 }
109 Self::InBroadcast => {
110 write!(f, "Broadcast channel is read-only")
111 }
112 Self::NotAMember => write!(f, "not a member of the chat"),
113 Self::MissingKey => write!(f, "key is missing"),
114 }
115 }
116}
117
118#[derive(
123 Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord,
124)]
125pub struct ChatId(u32);
126
127impl ChatId {
128 pub const fn new(id: u32) -> ChatId {
130 ChatId(id)
131 }
132
133 pub fn is_unset(self) -> bool {
137 self.0 == 0
138 }
139
140 pub fn is_special(self) -> bool {
144 (0..=DC_CHAT_ID_LAST_SPECIAL.0).contains(&self.0)
145 }
146
147 pub fn is_trash(self) -> bool {
154 self == DC_CHAT_ID_TRASH
155 }
156
157 pub fn is_archived_link(self) -> bool {
164 self == DC_CHAT_ID_ARCHIVED_LINK
165 }
166
167 pub fn is_alldone_hint(self) -> bool {
176 self == DC_CHAT_ID_ALLDONE_HINT
177 }
178
179 pub(crate) fn lookup_by_message(msg: &Message) -> Option<Self> {
181 if msg.chat_id == DC_CHAT_ID_TRASH {
182 return None;
183 }
184 if msg.download_state == DownloadState::Undecipherable {
185 return None;
186 }
187 Some(msg.chat_id)
188 }
189
190 pub async fn lookup_by_contact(
195 context: &Context,
196 contact_id: ContactId,
197 ) -> Result<Option<Self>> {
198 let Some(chat_id_blocked) = ChatIdBlocked::lookup_by_contact(context, contact_id).await?
199 else {
200 return Ok(None);
201 };
202
203 let chat_id = match chat_id_blocked.blocked {
204 Blocked::Not | Blocked::Request => Some(chat_id_blocked.id),
205 Blocked::Yes => None,
206 };
207 Ok(chat_id)
208 }
209
210 pub(crate) async fn get_for_contact(context: &Context, contact_id: ContactId) -> Result<Self> {
218 ChatIdBlocked::get_for_contact(context, contact_id, Blocked::Not)
219 .await
220 .map(|chat| chat.id)
221 }
222
223 pub async fn create_for_contact(context: &Context, contact_id: ContactId) -> Result<Self> {
228 ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Not).await
229 }
230
231 pub(crate) async fn create_for_contact_with_blocked(
235 context: &Context,
236 contact_id: ContactId,
237 create_blocked: Blocked,
238 ) -> Result<Self> {
239 let chat_id = match ChatIdBlocked::lookup_by_contact(context, contact_id).await? {
240 Some(chat) => {
241 if create_blocked != Blocked::Not || chat.blocked == Blocked::Not {
242 return Ok(chat.id);
243 }
244 chat.id.set_blocked(context, Blocked::Not).await?;
245 chat.id
246 }
247 None => {
248 if Contact::real_exists_by_id(context, contact_id).await?
249 || contact_id == ContactId::SELF
250 {
251 let chat_id =
252 ChatIdBlocked::get_for_contact(context, contact_id, create_blocked)
253 .await
254 .map(|chat| chat.id)?;
255 ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat).await?;
256 chat_id
257 } else {
258 warn!(
259 context,
260 "Cannot create chat, contact {contact_id} does not exist."
261 );
262 bail!("Can not create chat for non-existing contact");
263 }
264 }
265 };
266 context.emit_msgs_changed_without_ids();
267 chatlist_events::emit_chatlist_changed(context);
268 chatlist_events::emit_chatlist_item_changed(context, chat_id);
269 Ok(chat_id)
270 }
271
272 pub(crate) async fn create_multiuser_record(
275 context: &Context,
276 chattype: Chattype,
277 grpid: &str,
278 grpname: &str,
279 create_blocked: Blocked,
280 param: Option<String>,
281 timestamp: i64,
282 ) -> Result<Self> {
283 let grpname = sanitize_single_line(grpname);
284 let timestamp = cmp::min(timestamp, smeared_time(context));
285 let row_id =
286 context.sql.insert(
287 "INSERT INTO chats (type, name, grpid, blocked, created_timestamp, protected, param) VALUES(?, ?, ?, ?, ?, 0, ?);",
288 (
289 chattype,
290 &grpname,
291 grpid,
292 create_blocked,
293 timestamp,
294 param.unwrap_or_default(),
295 ),
296 ).await?;
297
298 let chat_id = ChatId::new(u32::try_from(row_id)?);
299 let chat = Chat::load_from_db(context, chat_id).await?;
300
301 if chat.is_encrypted(context).await? {
302 chat_id.add_encrypted_msg(context, timestamp).await?;
303 }
304
305 info!(
306 context,
307 "Created group/mailinglist '{}' grpid={} as {}, blocked={}.",
308 &grpname,
309 grpid,
310 chat_id,
311 create_blocked,
312 );
313
314 Ok(chat_id)
315 }
316
317 async fn set_selfavatar_timestamp(self, context: &Context, timestamp: i64) -> Result<()> {
318 context
319 .sql
320 .execute(
321 "UPDATE contacts
322 SET selfavatar_sent=?
323 WHERE id IN(SELECT contact_id FROM chats_contacts WHERE chat_id=? AND add_timestamp >= remove_timestamp)",
324 (timestamp, self),
325 )
326 .await?;
327 Ok(())
328 }
329
330 pub(crate) async fn set_blocked(self, context: &Context, new_blocked: Blocked) -> Result<bool> {
334 if self.is_special() {
335 bail!("ignoring setting of Block-status for {self}");
336 }
337 let count = context
338 .sql
339 .execute(
340 "UPDATE chats SET blocked=?1 WHERE id=?2 AND blocked != ?1",
341 (new_blocked, self),
342 )
343 .await?;
344 Ok(count > 0)
345 }
346
347 pub async fn block(self, context: &Context) -> Result<()> {
349 self.block_ex(context, Sync).await
350 }
351
352 pub(crate) async fn block_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
353 let chat = Chat::load_from_db(context, self).await?;
354 let mut delete = false;
355
356 match chat.typ {
357 Chattype::OutBroadcast => {
358 bail!("Can't block chat of type {:?}", chat.typ)
359 }
360 Chattype::Single => {
361 for contact_id in get_chat_contacts(context, self).await? {
362 if contact_id != ContactId::SELF {
363 info!(
364 context,
365 "Blocking the contact {contact_id} to block 1:1 chat."
366 );
367 contact::set_blocked(context, Nosync, contact_id, true).await?;
368 }
369 }
370 }
371 Chattype::Group => {
372 info!(context, "Can't block groups yet, deleting the chat.");
373 delete = true;
374 }
375 Chattype::Mailinglist | Chattype::InBroadcast => {
376 if self.set_blocked(context, Blocked::Yes).await? {
377 context.emit_event(EventType::ChatModified(self));
378 }
379 }
380 }
381 chatlist_events::emit_chatlist_changed(context);
382
383 if sync.into() {
384 chat.sync(context, SyncAction::Block)
386 .await
387 .log_err(context)
388 .ok();
389 }
390 if delete {
391 self.delete_ex(context, Nosync).await?;
392 }
393 Ok(())
394 }
395
396 pub async fn unblock(self, context: &Context) -> Result<()> {
398 self.unblock_ex(context, Sync).await
399 }
400
401 pub(crate) async fn unblock_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
402 self.set_blocked(context, Blocked::Not).await?;
403
404 chatlist_events::emit_chatlist_changed(context);
405
406 if sync.into() {
407 let chat = Chat::load_from_db(context, self).await?;
408 chat.sync(context, SyncAction::Unblock)
412 .await
413 .log_err(context)
414 .ok();
415 }
416
417 Ok(())
418 }
419
420 pub async fn accept(self, context: &Context) -> Result<()> {
424 self.accept_ex(context, Sync).await
425 }
426
427 pub(crate) async fn accept_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
428 let chat = Chat::load_from_db(context, self).await?;
429
430 match chat.typ {
431 Chattype::Single | Chattype::Group | Chattype::OutBroadcast | Chattype::InBroadcast => {
432 for contact_id in get_chat_contacts(context, self).await? {
437 if contact_id != ContactId::SELF {
438 ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat)
439 .await?;
440 }
441 }
442 }
443 Chattype::Mailinglist => {
444 }
446 }
447
448 if self.set_blocked(context, Blocked::Not).await? {
449 context.emit_event(EventType::ChatModified(self));
450 chatlist_events::emit_chatlist_item_changed(context, self);
451 }
452
453 if sync.into() {
454 chat.sync(context, SyncAction::Accept)
455 .await
456 .log_err(context)
457 .ok();
458 }
459 Ok(())
460 }
461
462 async fn add_encrypted_msg(self, context: &Context, timestamp_sort: i64) -> Result<()> {
464 let text = stock_str::messages_e2e_encrypted(context).await;
465 add_info_msg_with_cmd(
466 context,
467 self,
468 &text,
469 SystemMessage::ChatE2ee,
470 timestamp_sort,
471 None,
472 None,
473 None,
474 None,
475 )
476 .await?;
477 Ok(())
478 }
479
480 pub async fn set_visibility(self, context: &Context, visibility: ChatVisibility) -> Result<()> {
482 self.set_visibility_ex(context, Sync, visibility).await
483 }
484
485 pub(crate) async fn set_visibility_ex(
486 self,
487 context: &Context,
488 sync: sync::Sync,
489 visibility: ChatVisibility,
490 ) -> Result<()> {
491 ensure!(
492 !self.is_special(),
493 "bad chat_id, can not be special chat: {self}"
494 );
495
496 context
497 .sql
498 .transaction(move |transaction| {
499 if visibility == ChatVisibility::Archived {
500 transaction.execute(
501 "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;",
502 (MessageState::InNoticed, self, MessageState::InFresh),
503 )?;
504 }
505 transaction.execute(
506 "UPDATE chats SET archived=? WHERE id=?;",
507 (visibility, self),
508 )?;
509 Ok(())
510 })
511 .await?;
512
513 if visibility == ChatVisibility::Archived {
514 start_chat_ephemeral_timers(context, self).await?;
515 }
516
517 context.emit_msgs_changed_without_ids();
518 chatlist_events::emit_chatlist_changed(context);
519 chatlist_events::emit_chatlist_item_changed(context, self);
520
521 if sync.into() {
522 let chat = Chat::load_from_db(context, self).await?;
523 chat.sync(context, SyncAction::SetVisibility(visibility))
524 .await
525 .log_err(context)
526 .ok();
527 }
528 Ok(())
529 }
530
531 pub async fn unarchive_if_not_muted(
539 self,
540 context: &Context,
541 msg_state: MessageState,
542 ) -> Result<()> {
543 if msg_state != MessageState::InFresh {
544 context
545 .sql
546 .execute(
547 "UPDATE chats SET archived=0 WHERE id=? AND archived=1 \
548 AND NOT(muted_until=-1 OR muted_until>?)",
549 (self, time()),
550 )
551 .await?;
552 return Ok(());
553 }
554 let chat = Chat::load_from_db(context, self).await?;
555 if chat.visibility != ChatVisibility::Archived {
556 return Ok(());
557 }
558 if chat.is_muted() {
559 let unread_cnt = context
560 .sql
561 .count(
562 "SELECT COUNT(*)
563 FROM msgs
564 WHERE state=?
565 AND hidden=0
566 AND chat_id=?",
567 (MessageState::InFresh, self),
568 )
569 .await?;
570 if unread_cnt == 1 {
571 context.emit_msgs_changed_without_msg_id(DC_CHAT_ID_ARCHIVED_LINK);
573 }
574 return Ok(());
575 }
576 context
577 .sql
578 .execute("UPDATE chats SET archived=0 WHERE id=?", (self,))
579 .await?;
580 Ok(())
581 }
582
583 pub(crate) fn emit_msg_event(self, context: &Context, msg_id: MsgId, important: bool) {
586 if important {
587 debug_assert!(!msg_id.is_unset());
588
589 context.emit_incoming_msg(self, msg_id);
590 } else {
591 context.emit_msgs_changed(self, msg_id);
592 }
593 }
594
595 pub async fn delete(self, context: &Context) -> Result<()> {
597 self.delete_ex(context, Sync).await
598 }
599
600 pub(crate) async fn delete_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
601 ensure!(
602 !self.is_special(),
603 "bad chat_id, can not be a special chat: {self}"
604 );
605
606 let chat = Chat::load_from_db(context, self).await?;
607 let delete_msgs_target = context.get_delete_msgs_target().await?;
608 let sync_id = match sync {
609 Nosync => None,
610 Sync => chat.get_sync_id(context).await?,
611 };
612
613 context
614 .sql
615 .transaction(|transaction| {
616 transaction.execute(
617 "UPDATE imap SET target=? WHERE rfc724_mid IN (SELECT rfc724_mid FROM msgs WHERE chat_id=?)",
618 (delete_msgs_target, self,),
619 )?;
620 transaction.execute(
621 "DELETE FROM smtp WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?)",
622 (self,),
623 )?;
624 transaction.execute(
625 "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?)",
626 (self,),
627 )?;
628 transaction.execute("DELETE FROM msgs WHERE chat_id=?", (self,))?;
629 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (self,))?;
630 transaction.execute("DELETE FROM chats WHERE id=?", (self,))?;
631 Ok(())
632 })
633 .await?;
634
635 context.emit_event(EventType::ChatDeleted { chat_id: self });
636 context.emit_msgs_changed_without_ids();
637
638 if let Some(id) = sync_id {
639 self::sync(context, id, SyncAction::Delete)
640 .await
641 .log_err(context)
642 .ok();
643 }
644
645 if chat.is_self_talk() {
646 let mut msg = Message::new_text(stock_str::self_deleted_msg_body(context).await);
647 add_device_msg(context, None, Some(&mut msg)).await?;
648 }
649 chatlist_events::emit_chatlist_changed(context);
650
651 context
652 .set_config_internal(Config::LastHousekeeping, None)
653 .await?;
654 context.scheduler.interrupt_inbox().await;
655
656 Ok(())
657 }
658
659 pub async fn set_draft(self, context: &Context, mut msg: Option<&mut Message>) -> Result<()> {
663 if self.is_special() {
664 return Ok(());
665 }
666
667 let changed = match &mut msg {
668 None => self.maybe_delete_draft(context).await?,
669 Some(msg) => self.do_set_draft(context, msg).await?,
670 };
671
672 if changed {
673 if msg.is_some() {
674 match self.get_draft_msg_id(context).await? {
675 Some(msg_id) => context.emit_msgs_changed(self, msg_id),
676 None => context.emit_msgs_changed_without_msg_id(self),
677 }
678 } else {
679 context.emit_msgs_changed_without_msg_id(self)
680 }
681 }
682
683 Ok(())
684 }
685
686 async fn get_draft_msg_id(self, context: &Context) -> Result<Option<MsgId>> {
688 let msg_id: Option<MsgId> = context
689 .sql
690 .query_get_value(
691 "SELECT id FROM msgs WHERE chat_id=? AND state=?;",
692 (self, MessageState::OutDraft),
693 )
694 .await?;
695 Ok(msg_id)
696 }
697
698 pub async fn get_draft(self, context: &Context) -> Result<Option<Message>> {
700 if self.is_special() {
701 return Ok(None);
702 }
703 match self.get_draft_msg_id(context).await? {
704 Some(draft_msg_id) => {
705 let msg = Message::load_from_db(context, draft_msg_id).await?;
706 Ok(Some(msg))
707 }
708 None => Ok(None),
709 }
710 }
711
712 async fn maybe_delete_draft(self, context: &Context) -> Result<bool> {
716 Ok(context
717 .sql
718 .execute(
719 "DELETE FROM msgs WHERE chat_id=? AND state=?",
720 (self, MessageState::OutDraft),
721 )
722 .await?
723 > 0)
724 }
725
726 async fn do_set_draft(self, context: &Context, msg: &mut Message) -> Result<bool> {
729 match msg.viewtype {
730 Viewtype::Unknown => bail!("Can not set draft of unknown type."),
731 Viewtype::Text => {
732 if msg.text.is_empty() && msg.in_reply_to.is_none_or_empty() {
733 bail!("No text and no quote in draft");
734 }
735 }
736 _ => {
737 if msg.viewtype == Viewtype::File {
738 if let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg)
739 .filter(|&(vt, _)| vt == Viewtype::Webxdc || vt == Viewtype::Vcard)
744 {
745 msg.viewtype = better_type;
746 }
747 }
748 if msg.viewtype == Viewtype::Vcard {
749 let blob = msg
750 .param
751 .get_file_blob(context)?
752 .context("no file stored in params")?;
753 msg.try_set_vcard(context, &blob.to_abs_path()).await?;
754 }
755 }
756 }
757
758 msg.state = MessageState::OutDraft;
761 msg.chat_id = self;
762
763 if !msg.id.is_special() {
765 if let Some(old_draft) = self.get_draft(context).await? {
766 if old_draft.id == msg.id
767 && old_draft.chat_id == self
768 && old_draft.state == MessageState::OutDraft
769 {
770 let affected_rows = context
771 .sql.execute(
772 "UPDATE msgs
773 SET timestamp=?1,type=?2,txt=?3,txt_normalized=?4,param=?5,mime_in_reply_to=?6
774 WHERE id=?7
775 AND (type <> ?2
776 OR txt <> ?3
777 OR txt_normalized <> ?4
778 OR param <> ?5
779 OR mime_in_reply_to <> ?6);",
780 (
781 time(),
782 msg.viewtype,
783 &msg.text,
784 message::normalize_text(&msg.text),
785 msg.param.to_string(),
786 msg.in_reply_to.as_deref().unwrap_or_default(),
787 msg.id,
788 ),
789 ).await?;
790 return Ok(affected_rows > 0);
791 }
792 }
793 }
794
795 let row_id = context
796 .sql
797 .transaction(|transaction| {
798 transaction.execute(
800 "DELETE FROM msgs WHERE chat_id=? AND state=?",
801 (self, MessageState::OutDraft),
802 )?;
803
804 transaction.execute(
806 "INSERT INTO msgs (
807 chat_id,
808 rfc724_mid,
809 from_id,
810 timestamp,
811 type,
812 state,
813 txt,
814 txt_normalized,
815 param,
816 hidden,
817 mime_in_reply_to)
818 VALUES (?,?,?,?,?,?,?,?,?,?,?);",
819 (
820 self,
821 &msg.rfc724_mid,
822 ContactId::SELF,
823 time(),
824 msg.viewtype,
825 MessageState::OutDraft,
826 &msg.text,
827 message::normalize_text(&msg.text),
828 msg.param.to_string(),
829 1,
830 msg.in_reply_to.as_deref().unwrap_or_default(),
831 ),
832 )?;
833
834 Ok(transaction.last_insert_rowid())
835 })
836 .await?;
837 msg.id = MsgId::new(row_id.try_into()?);
838 Ok(true)
839 }
840
841 pub async fn get_msg_cnt(self, context: &Context) -> Result<usize> {
843 let count = context
844 .sql
845 .count(
846 "SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id=?",
847 (self,),
848 )
849 .await?;
850 Ok(count)
851 }
852
853 pub async fn get_fresh_msg_cnt(self, context: &Context) -> Result<usize> {
855 let count = if self.is_archived_link() {
866 context
867 .sql
868 .count(
869 "SELECT COUNT(DISTINCT(m.chat_id))
870 FROM msgs m
871 LEFT JOIN chats c ON m.chat_id=c.id
872 WHERE m.state=10
873 and m.hidden=0
874 AND m.chat_id>9
875 AND c.blocked=0
876 AND c.archived=1
877 ",
878 (),
879 )
880 .await?
881 } else {
882 context
883 .sql
884 .count(
885 "SELECT COUNT(*)
886 FROM msgs
887 WHERE state=?
888 AND hidden=0
889 AND chat_id=?;",
890 (MessageState::InFresh, self),
891 )
892 .await?
893 };
894 Ok(count)
895 }
896
897 pub(crate) async fn created_timestamp(self, context: &Context) -> Result<i64> {
898 Ok(context
899 .sql
900 .query_get_value("SELECT created_timestamp FROM chats WHERE id=?", (self,))
901 .await?
902 .unwrap_or(0))
903 }
904
905 pub(crate) async fn get_timestamp(self, context: &Context) -> Result<Option<i64>> {
908 let timestamp = context
909 .sql
910 .query_get_value(
911 "SELECT MAX(timestamp)
912 FROM msgs
913 WHERE chat_id=?
914 HAVING COUNT(*) > 0",
915 (self,),
916 )
917 .await?;
918 Ok(timestamp)
919 }
920
921 pub async fn get_similar_chat_ids(self, context: &Context) -> Result<Vec<(ChatId, f64)>> {
927 let intersection = context
929 .sql
930 .query_map_vec(
931 "SELECT y.chat_id, SUM(x.contact_id = y.contact_id)
932 FROM chats_contacts as x
933 JOIN chats_contacts as y
934 WHERE x.contact_id > 9
935 AND y.contact_id > 9
936 AND x.add_timestamp >= x.remove_timestamp
937 AND y.add_timestamp >= y.remove_timestamp
938 AND x.chat_id=?
939 AND y.chat_id<>x.chat_id
940 AND y.chat_id>?
941 GROUP BY y.chat_id",
942 (self, DC_CHAT_ID_LAST_SPECIAL),
943 |row| {
944 let chat_id: ChatId = row.get(0)?;
945 let intersection: f64 = row.get(1)?;
946 Ok((chat_id, intersection))
947 },
948 )
949 .await
950 .context("failed to calculate member set intersections")?;
951
952 let chat_size: HashMap<ChatId, f64> = context
953 .sql
954 .query_map_collect(
955 "SELECT chat_id, count(*) AS n
956 FROM chats_contacts
957 WHERE contact_id > ? AND chat_id > ?
958 AND add_timestamp >= remove_timestamp
959 GROUP BY chat_id",
960 (ContactId::LAST_SPECIAL, DC_CHAT_ID_LAST_SPECIAL),
961 |row| {
962 let chat_id: ChatId = row.get(0)?;
963 let size: f64 = row.get(1)?;
964 Ok((chat_id, size))
965 },
966 )
967 .await
968 .context("failed to count chat member sizes")?;
969
970 let our_chat_size = chat_size.get(&self).copied().unwrap_or_default();
971 let mut chats_with_metrics = Vec::new();
972 for (chat_id, intersection_size) in intersection {
973 if intersection_size > 0.0 {
974 let other_chat_size = chat_size.get(&chat_id).copied().unwrap_or_default();
975 let union_size = our_chat_size + other_chat_size - intersection_size;
976 let metric = intersection_size / union_size;
977 chats_with_metrics.push((chat_id, metric))
978 }
979 }
980 chats_with_metrics.sort_unstable_by(|(chat_id1, metric1), (chat_id2, metric2)| {
981 metric2
982 .partial_cmp(metric1)
983 .unwrap_or(chat_id2.cmp(chat_id1))
984 });
985
986 let mut res = Vec::new();
988 let now = time();
989 for (chat_id, metric) in chats_with_metrics {
990 if let Some(chat_timestamp) = chat_id.get_timestamp(context).await? {
991 if now > chat_timestamp + 42 * 24 * 3600 {
992 continue;
994 }
995 }
996
997 if metric < 0.1 {
998 break;
1000 }
1001
1002 let chat = Chat::load_from_db(context, chat_id).await?;
1003 if chat.typ != Chattype::Group {
1004 continue;
1005 }
1006
1007 match chat.visibility {
1008 ChatVisibility::Normal | ChatVisibility::Pinned => {}
1009 ChatVisibility::Archived => continue,
1010 }
1011
1012 res.push((chat_id, metric));
1013 if res.len() >= 5 {
1014 break;
1015 }
1016 }
1017
1018 Ok(res)
1019 }
1020
1021 pub async fn get_similar_chatlist(self, context: &Context) -> Result<Chatlist> {
1025 let chat_ids: Vec<ChatId> = self
1026 .get_similar_chat_ids(context)
1027 .await
1028 .context("failed to get similar chat IDs")?
1029 .into_iter()
1030 .map(|(chat_id, _metric)| chat_id)
1031 .collect();
1032 let chatlist = Chatlist::from_chat_ids(context, &chat_ids).await?;
1033 Ok(chatlist)
1034 }
1035
1036 pub(crate) async fn get_param(self, context: &Context) -> Result<Params> {
1037 let res: Option<String> = context
1038 .sql
1039 .query_get_value("SELECT param FROM chats WHERE id=?", (self,))
1040 .await?;
1041 Ok(res
1042 .map(|s| s.parse().unwrap_or_default())
1043 .unwrap_or_default())
1044 }
1045
1046 pub(crate) async fn is_unpromoted(self, context: &Context) -> Result<bool> {
1048 let param = self.get_param(context).await?;
1049 let unpromoted = param.get_bool(Param::Unpromoted).unwrap_or_default();
1050 Ok(unpromoted)
1051 }
1052
1053 pub(crate) async fn is_promoted(self, context: &Context) -> Result<bool> {
1055 let promoted = !self.is_unpromoted(context).await?;
1056 Ok(promoted)
1057 }
1058
1059 pub async fn is_self_talk(self, context: &Context) -> Result<bool> {
1061 Ok(self.get_param(context).await?.exists(Param::Selftalk))
1062 }
1063
1064 pub async fn is_device_talk(self, context: &Context) -> Result<bool> {
1066 Ok(self.get_param(context).await?.exists(Param::Devicetalk))
1067 }
1068
1069 async fn parent_query<T, F>(
1070 self,
1071 context: &Context,
1072 fields: &str,
1073 state_out_min: MessageState,
1074 f: F,
1075 ) -> Result<Option<T>>
1076 where
1077 F: Send + FnOnce(&rusqlite::Row) -> rusqlite::Result<T>,
1078 T: Send + 'static,
1079 {
1080 let sql = &context.sql;
1081 let query = format!(
1082 "SELECT {fields} \
1083 FROM msgs \
1084 WHERE chat_id=? \
1085 AND ((state BETWEEN {} AND {}) OR (state >= {})) \
1086 AND NOT hidden \
1087 AND download_state={} \
1088 AND from_id != {} \
1089 ORDER BY timestamp DESC, id DESC \
1090 LIMIT 1;",
1091 MessageState::InFresh as u32,
1092 MessageState::InSeen as u32,
1093 state_out_min as u32,
1094 DownloadState::Done as u32,
1097 ContactId::INFO.to_u32(),
1100 );
1101 sql.query_row_optional(&query, (self,), f).await
1102 }
1103
1104 async fn get_parent_mime_headers(
1105 self,
1106 context: &Context,
1107 state_out_min: MessageState,
1108 ) -> Result<Option<(String, String, String)>> {
1109 self.parent_query(
1110 context,
1111 "rfc724_mid, mime_in_reply_to, IFNULL(mime_references, '')",
1112 state_out_min,
1113 |row: &rusqlite::Row| {
1114 let rfc724_mid: String = row.get(0)?;
1115 let mime_in_reply_to: String = row.get(1)?;
1116 let mime_references: String = row.get(2)?;
1117 Ok((rfc724_mid, mime_in_reply_to, mime_references))
1118 },
1119 )
1120 .await
1121 }
1122
1123 pub async fn get_encryption_info(self, context: &Context) -> Result<String> {
1131 let chat = Chat::load_from_db(context, self).await?;
1132 if !chat.is_encrypted(context).await? {
1133 return Ok(stock_str::encr_none(context).await);
1134 }
1135
1136 let mut ret = stock_str::e2e_available(context).await + "\n";
1137
1138 for &contact_id in get_chat_contacts(context, self)
1139 .await?
1140 .iter()
1141 .filter(|&contact_id| !contact_id.is_special())
1142 {
1143 let contact = Contact::get_by_id(context, contact_id).await?;
1144 let addr = contact.get_addr();
1145 logged_debug_assert!(
1146 context,
1147 contact.is_key_contact(),
1148 "get_encryption_info: contact {contact_id} is not a key-contact."
1149 );
1150 let fingerprint = contact
1151 .fingerprint()
1152 .context("Contact does not have a fingerprint in encrypted chat")?;
1153 if contact.public_key(context).await?.is_some() {
1154 ret += &format!("\n{addr}\n{fingerprint}\n");
1155 } else {
1156 ret += &format!("\n{addr}\n(key missing)\n{fingerprint}\n");
1157 }
1158 }
1159
1160 Ok(ret.trim().to_string())
1161 }
1162
1163 pub fn to_u32(self) -> u32 {
1168 self.0
1169 }
1170
1171 pub(crate) async fn reset_gossiped_timestamp(self, context: &Context) -> Result<()> {
1172 context
1173 .sql
1174 .execute("DELETE FROM gossip_timestamp WHERE chat_id=?", (self,))
1175 .await?;
1176 Ok(())
1177 }
1178
1179 pub(crate) async fn calc_sort_timestamp(
1188 self,
1189 context: &Context,
1190 message_timestamp: i64,
1191 always_sort_to_bottom: bool,
1192 received: bool,
1193 incoming: bool,
1194 ) -> Result<i64> {
1195 let mut sort_timestamp = cmp::min(message_timestamp, smeared_time(context));
1196
1197 let last_msg_time: Option<i64> = if always_sort_to_bottom {
1198 context
1204 .sql
1205 .query_get_value(
1206 "SELECT MAX(timestamp)
1207 FROM msgs
1208 WHERE chat_id=? AND state!=?
1209 HAVING COUNT(*) > 0",
1210 (self, MessageState::OutDraft),
1211 )
1212 .await?
1213 } else if received {
1214 context
1225 .sql
1226 .query_row_optional(
1227 "SELECT MAX(timestamp), MAX(IIF(state=?,timestamp_sent,0))
1228 FROM msgs
1229 WHERE chat_id=? AND hidden=0 AND state>?
1230 HAVING COUNT(*) > 0",
1231 (MessageState::InSeen, self, MessageState::InFresh),
1232 |row| {
1233 let ts: i64 = row.get(0)?;
1234 let ts_sent_seen: i64 = row.get(1)?;
1235 Ok((ts, ts_sent_seen))
1236 },
1237 )
1238 .await?
1239 .and_then(|(ts, ts_sent_seen)| {
1240 match incoming || ts_sent_seen <= message_timestamp {
1241 true => Some(ts),
1242 false => None,
1243 }
1244 })
1245 } else {
1246 None
1247 };
1248
1249 if let Some(last_msg_time) = last_msg_time {
1250 if last_msg_time > sort_timestamp {
1251 sort_timestamp = last_msg_time;
1252 }
1253 }
1254
1255 Ok(sort_timestamp)
1256 }
1257}
1258
1259impl std::fmt::Display for ChatId {
1260 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1261 if self.is_trash() {
1262 write!(f, "Chat#Trash")
1263 } else if self.is_archived_link() {
1264 write!(f, "Chat#ArchivedLink")
1265 } else if self.is_alldone_hint() {
1266 write!(f, "Chat#AlldoneHint")
1267 } else if self.is_special() {
1268 write!(f, "Chat#Special{}", self.0)
1269 } else {
1270 write!(f, "Chat#{}", self.0)
1271 }
1272 }
1273}
1274
1275impl rusqlite::types::ToSql for ChatId {
1280 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
1281 let val = rusqlite::types::Value::Integer(i64::from(self.0));
1282 let out = rusqlite::types::ToSqlOutput::Owned(val);
1283 Ok(out)
1284 }
1285}
1286
1287impl rusqlite::types::FromSql for ChatId {
1289 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
1290 i64::column_result(value).and_then(|val| {
1291 if 0 <= val && val <= i64::from(u32::MAX) {
1292 Ok(ChatId::new(val as u32))
1293 } else {
1294 Err(rusqlite::types::FromSqlError::OutOfRange(val))
1295 }
1296 })
1297 }
1298}
1299
1300#[derive(Debug, Clone, Deserialize, Serialize)]
1305pub struct Chat {
1306 pub id: ChatId,
1308
1309 pub typ: Chattype,
1311
1312 pub name: String,
1314
1315 pub visibility: ChatVisibility,
1317
1318 pub grpid: String,
1321
1322 pub blocked: Blocked,
1324
1325 pub param: Params,
1327
1328 is_sending_locations: bool,
1330
1331 pub mute_duration: MuteDuration,
1333}
1334
1335impl Chat {
1336 pub async fn load_from_db(context: &Context, chat_id: ChatId) -> Result<Self> {
1338 let mut chat = context
1339 .sql
1340 .query_row(
1341 "SELECT c.type, c.name, c.grpid, c.param, c.archived,
1342 c.blocked, c.locations_send_until, c.muted_until
1343 FROM chats c
1344 WHERE c.id=?;",
1345 (chat_id,),
1346 |row| {
1347 let c = Chat {
1348 id: chat_id,
1349 typ: row.get(0)?,
1350 name: row.get::<_, String>(1)?,
1351 grpid: row.get::<_, String>(2)?,
1352 param: row.get::<_, String>(3)?.parse().unwrap_or_default(),
1353 visibility: row.get(4)?,
1354 blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(),
1355 is_sending_locations: row.get(6)?,
1356 mute_duration: row.get(7)?,
1357 };
1358 Ok(c)
1359 },
1360 )
1361 .await
1362 .context(format!("Failed loading chat {chat_id} from database"))?;
1363
1364 if chat.id.is_archived_link() {
1365 chat.name = stock_str::archived_chats(context).await;
1366 } else {
1367 if chat.typ == Chattype::Single && chat.name.is_empty() {
1368 let mut chat_name = "Err [Name not found]".to_owned();
1371 match get_chat_contacts(context, chat.id).await {
1372 Ok(contacts) => {
1373 if let Some(contact_id) = contacts.first() {
1374 if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1375 contact.get_display_name().clone_into(&mut chat_name);
1376 }
1377 }
1378 }
1379 Err(err) => {
1380 error!(
1381 context,
1382 "Failed to load contacts for {}: {:#}.", chat.id, err
1383 );
1384 }
1385 }
1386 chat.name = chat_name;
1387 }
1388 if chat.param.exists(Param::Selftalk) {
1389 chat.name = stock_str::saved_messages(context).await;
1390 } else if chat.param.exists(Param::Devicetalk) {
1391 chat.name = stock_str::device_messages(context).await;
1392 }
1393 }
1394
1395 Ok(chat)
1396 }
1397
1398 pub fn is_self_talk(&self) -> bool {
1400 self.param.exists(Param::Selftalk)
1401 }
1402
1403 pub fn is_device_talk(&self) -> bool {
1405 self.param.exists(Param::Devicetalk)
1406 }
1407
1408 pub fn is_mailing_list(&self) -> bool {
1410 self.typ == Chattype::Mailinglist
1411 }
1412
1413 pub(crate) async fn why_cant_send(&self, context: &Context) -> Result<Option<CantSendReason>> {
1417 self.why_cant_send_ex(context, &|_| false).await
1418 }
1419
1420 pub(crate) async fn why_cant_send_ex(
1421 &self,
1422 context: &Context,
1423 skip_fn: &(dyn Send + Sync + Fn(&CantSendReason) -> bool),
1424 ) -> Result<Option<CantSendReason>> {
1425 use CantSendReason::*;
1426 if self.id.is_special() {
1429 let reason = SpecialChat;
1430 if !skip_fn(&reason) {
1431 return Ok(Some(reason));
1432 }
1433 }
1434 if self.is_device_talk() {
1435 let reason = DeviceChat;
1436 if !skip_fn(&reason) {
1437 return Ok(Some(reason));
1438 }
1439 }
1440 if self.is_contact_request() {
1441 let reason = ContactRequest;
1442 if !skip_fn(&reason) {
1443 return Ok(Some(reason));
1444 }
1445 }
1446 if self.is_mailing_list() && self.get_mailinglist_addr().is_none_or_empty() {
1447 let reason = ReadOnlyMailingList;
1448 if !skip_fn(&reason) {
1449 return Ok(Some(reason));
1450 }
1451 }
1452 if self.typ == Chattype::InBroadcast {
1453 let reason = InBroadcast;
1454 if !skip_fn(&reason) {
1455 return Ok(Some(reason));
1456 }
1457 }
1458
1459 let reason = NotAMember;
1461 if !skip_fn(&reason) && !self.is_self_in_chat(context).await? {
1462 return Ok(Some(reason));
1463 }
1464
1465 let reason = MissingKey;
1466 if !skip_fn(&reason) && self.typ == Chattype::Single {
1467 let contact_ids = get_chat_contacts(context, self.id).await?;
1468 if let Some(contact_id) = contact_ids.first() {
1469 let contact = Contact::get_by_id(context, *contact_id).await?;
1470 if contact.is_key_contact() && contact.public_key(context).await?.is_none() {
1471 return Ok(Some(reason));
1472 }
1473 }
1474 }
1475
1476 Ok(None)
1477 }
1478
1479 pub async fn can_send(&self, context: &Context) -> Result<bool> {
1483 Ok(self.why_cant_send(context).await?.is_none())
1484 }
1485
1486 pub async fn is_self_in_chat(&self, context: &Context) -> Result<bool> {
1490 match self.typ {
1491 Chattype::Single | Chattype::OutBroadcast | Chattype::Mailinglist => Ok(true),
1492 Chattype::Group => is_contact_in_chat(context, self.id, ContactId::SELF).await,
1493 Chattype::InBroadcast => Ok(false),
1494 }
1495 }
1496
1497 pub(crate) async fn update_param(&mut self, context: &Context) -> Result<()> {
1498 context
1499 .sql
1500 .execute(
1501 "UPDATE chats SET param=? WHERE id=?",
1502 (self.param.to_string(), self.id),
1503 )
1504 .await?;
1505 Ok(())
1506 }
1507
1508 pub fn get_id(&self) -> ChatId {
1510 self.id
1511 }
1512
1513 pub fn get_type(&self) -> Chattype {
1515 self.typ
1516 }
1517
1518 pub fn get_name(&self) -> &str {
1520 &self.name
1521 }
1522
1523 pub fn get_mailinglist_addr(&self) -> Option<&str> {
1525 self.param.get(Param::ListPost)
1526 }
1527
1528 pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1530 if self.id.is_archived_link() {
1531 return Ok(Some(get_archive_icon(context).await?));
1534 } else if self.is_device_talk() {
1535 return Ok(Some(get_device_icon(context).await?));
1536 } else if self.is_self_talk() {
1537 return Ok(Some(get_saved_messages_icon(context).await?));
1538 } else if !self.is_encrypted(context).await? {
1539 return Ok(Some(get_abs_path(
1541 context,
1542 Path::new(&get_unencrypted_icon(context).await?),
1543 )));
1544 } else if self.typ == Chattype::Single {
1545 let contacts = get_chat_contacts(context, self.id).await?;
1549 if let Some(contact_id) = contacts.first() {
1550 let contact = Contact::get_by_id(context, *contact_id).await?;
1551 return contact.get_profile_image(context).await;
1552 }
1553 } else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
1554 if !image_rel.is_empty() {
1556 return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
1557 }
1558 }
1559 Ok(None)
1560 }
1561
1562 pub async fn get_color(&self, context: &Context) -> Result<u32> {
1568 let mut color = 0;
1569
1570 if self.typ == Chattype::Single {
1571 let contacts = get_chat_contacts(context, self.id).await?;
1572 if let Some(contact_id) = contacts.first() {
1573 if let Ok(contact) = Contact::get_by_id(context, *contact_id).await {
1574 color = contact.get_color();
1575 }
1576 }
1577 } else if !self.grpid.is_empty() {
1578 color = str_to_color(&self.grpid);
1579 } else {
1580 color = str_to_color(&self.name);
1581 }
1582
1583 Ok(color)
1584 }
1585
1586 pub async fn get_info(&self, context: &Context) -> Result<ChatInfo> {
1591 let draft = match self.id.get_draft(context).await? {
1592 Some(message) => message.text,
1593 _ => String::new(),
1594 };
1595 Ok(ChatInfo {
1596 id: self.id,
1597 type_: self.typ as u32,
1598 name: self.name.clone(),
1599 archived: self.visibility == ChatVisibility::Archived,
1600 param: self.param.to_string(),
1601 is_sending_locations: self.is_sending_locations,
1602 color: self.get_color(context).await?,
1603 profile_image: self
1604 .get_profile_image(context)
1605 .await?
1606 .unwrap_or_else(std::path::PathBuf::new),
1607 draft,
1608 is_muted: self.is_muted(),
1609 ephemeral_timer: self.id.get_ephemeral_timer(context).await?,
1610 })
1611 }
1612
1613 pub fn get_visibility(&self) -> ChatVisibility {
1615 self.visibility
1616 }
1617
1618 pub fn is_contact_request(&self) -> bool {
1623 self.blocked == Blocked::Request
1624 }
1625
1626 pub fn is_unpromoted(&self) -> bool {
1628 self.param.get_bool(Param::Unpromoted).unwrap_or_default()
1629 }
1630
1631 pub fn is_promoted(&self) -> bool {
1634 !self.is_unpromoted()
1635 }
1636
1637 pub async fn is_encrypted(&self, context: &Context) -> Result<bool> {
1639 let is_encrypted = match self.typ {
1640 Chattype::Single => {
1641 match context
1642 .sql
1643 .query_row_optional(
1644 "SELECT cc.contact_id, c.fingerprint<>''
1645 FROM chats_contacts cc LEFT JOIN contacts c
1646 ON c.id=cc.contact_id
1647 WHERE cc.chat_id=?
1648 ",
1649 (self.id,),
1650 |row| {
1651 let id: ContactId = row.get(0)?;
1652 let is_key: bool = row.get(1)?;
1653 Ok((id, is_key))
1654 },
1655 )
1656 .await?
1657 {
1658 Some((id, is_key)) => is_key || id == ContactId::DEVICE,
1659 None => true,
1660 }
1661 }
1662 Chattype::Group => {
1663 !self.grpid.is_empty()
1665 }
1666 Chattype::Mailinglist => false,
1667 Chattype::OutBroadcast | Chattype::InBroadcast => true,
1668 };
1669 Ok(is_encrypted)
1670 }
1671
1672 pub fn is_sending_locations(&self) -> bool {
1674 self.is_sending_locations
1675 }
1676
1677 pub fn is_muted(&self) -> bool {
1679 match self.mute_duration {
1680 MuteDuration::NotMuted => false,
1681 MuteDuration::Forever => true,
1682 MuteDuration::Until(when) => when > SystemTime::now(),
1683 }
1684 }
1685
1686 pub(crate) async fn member_list_timestamp(&self, context: &Context) -> Result<i64> {
1688 if let Some(member_list_timestamp) = self.param.get_i64(Param::MemberListTimestamp) {
1689 Ok(member_list_timestamp)
1690 } else {
1691 Ok(self.id.created_timestamp(context).await?)
1692 }
1693 }
1694
1695 pub(crate) async fn member_list_is_stale(&self, context: &Context) -> Result<bool> {
1701 let now = time();
1702 let member_list_ts = self.member_list_timestamp(context).await?;
1703 let is_stale = now.saturating_add(TIMESTAMP_SENT_TOLERANCE)
1704 >= member_list_ts.saturating_add(60 * 24 * 3600);
1705 Ok(is_stale)
1706 }
1707
1708 async fn prepare_msg_raw(
1714 &mut self,
1715 context: &Context,
1716 msg: &mut Message,
1717 update_msg_id: Option<MsgId>,
1718 ) -> Result<()> {
1719 let mut to_id = 0;
1720 let mut location_id = 0;
1721
1722 if msg.rfc724_mid.is_empty() {
1723 msg.rfc724_mid = create_outgoing_rfc724_mid();
1724 }
1725
1726 if self.typ == Chattype::Single {
1727 if let Some(id) = context
1728 .sql
1729 .query_get_value(
1730 "SELECT contact_id FROM chats_contacts WHERE chat_id=?;",
1731 (self.id,),
1732 )
1733 .await?
1734 {
1735 to_id = id;
1736 } else {
1737 error!(
1738 context,
1739 "Cannot send message, contact for {} not found.", self.id,
1740 );
1741 bail!("Cannot set message, contact for {} not found.", self.id);
1742 }
1743 } else if matches!(self.typ, Chattype::Group | Chattype::OutBroadcast)
1744 && self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
1745 {
1746 msg.param.set_int(Param::AttachGroupImage, 1);
1747 self.param
1748 .remove(Param::Unpromoted)
1749 .set_i64(Param::GroupNameTimestamp, msg.timestamp_sort);
1750 self.update_param(context).await?;
1751 context
1757 .sync_qr_code_tokens(Some(self.grpid.as_str()))
1758 .await
1759 .log_err(context)
1760 .ok();
1761 }
1762
1763 let is_bot = context.get_config_bool(Config::Bot).await?;
1764 msg.param
1765 .set_optional(Param::Bot, Some("1").filter(|_| is_bot));
1766
1767 let new_references;
1771 if self.is_self_talk() {
1772 new_references = String::new();
1775 } else if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
1776 self
1782 .id
1783 .get_parent_mime_headers(context, MessageState::OutPending)
1784 .await?
1785 {
1786 if msg.in_reply_to.is_none() && !parent_rfc724_mid.is_empty() {
1790 msg.in_reply_to = Some(parent_rfc724_mid.clone());
1791 }
1792
1793 let parent_references = if parent_references.is_empty() {
1803 parent_in_reply_to
1804 } else {
1805 parent_references
1806 };
1807
1808 let mut references_vec: Vec<&str> = parent_references.rsplit(' ').take(2).collect();
1811 references_vec.reverse();
1812
1813 if !parent_rfc724_mid.is_empty()
1814 && !references_vec.contains(&parent_rfc724_mid.as_str())
1815 {
1816 references_vec.push(&parent_rfc724_mid)
1817 }
1818
1819 if references_vec.is_empty() {
1820 new_references = msg.rfc724_mid.clone();
1823 } else {
1824 new_references = references_vec.join(" ");
1825 }
1826 } else {
1827 new_references = msg.rfc724_mid.clone();
1833 }
1834
1835 if msg.param.exists(Param::SetLatitude) {
1837 if let Ok(row_id) = context
1838 .sql
1839 .insert(
1840 "INSERT INTO locations \
1841 (timestamp,from_id,chat_id, latitude,longitude,independent)\
1842 VALUES (?,?,?, ?,?,1);",
1843 (
1844 msg.timestamp_sort,
1845 ContactId::SELF,
1846 self.id,
1847 msg.param.get_float(Param::SetLatitude).unwrap_or_default(),
1848 msg.param.get_float(Param::SetLongitude).unwrap_or_default(),
1849 ),
1850 )
1851 .await
1852 {
1853 location_id = row_id;
1854 }
1855 }
1856
1857 let ephemeral_timer = if msg.param.get_cmd() == SystemMessage::EphemeralTimerChanged {
1858 EphemeralTimer::Disabled
1859 } else {
1860 self.id.get_ephemeral_timer(context).await?
1861 };
1862 let ephemeral_timestamp = match ephemeral_timer {
1863 EphemeralTimer::Disabled => 0,
1864 EphemeralTimer::Enabled { duration } => time().saturating_add(duration.into()),
1865 };
1866
1867 let (msg_text, was_truncated) = truncate_msg_text(context, msg.text.clone()).await?;
1868 let new_mime_headers = if msg.has_html() {
1869 if msg.param.exists(Param::Forwarded) {
1870 msg.get_id().get_html(context).await?
1871 } else {
1872 msg.param.get(Param::SendHtml).map(|s| s.to_string())
1873 }
1874 } else {
1875 None
1876 };
1877 let new_mime_headers: Option<String> = new_mime_headers.map(|s| {
1878 let html_part = MimePart::new("text/html", s);
1879 let mut buffer = Vec::new();
1880 let cursor = Cursor::new(&mut buffer);
1881 html_part.write_part(cursor).ok();
1882 String::from_utf8_lossy(&buffer).to_string()
1883 });
1884 let new_mime_headers = new_mime_headers.or_else(|| match was_truncated {
1885 true => Some("Content-Type: text/plain; charset=utf-8\r\n\r\n".to_string() + &msg.text),
1889 false => None,
1890 });
1891 let new_mime_headers = match new_mime_headers {
1892 Some(h) => Some(tokio::task::block_in_place(move || {
1893 buf_compress(h.as_bytes())
1894 })?),
1895 None => None,
1896 };
1897
1898 msg.chat_id = self.id;
1899 msg.from_id = ContactId::SELF;
1900
1901 if let Some(update_msg_id) = update_msg_id {
1903 context
1904 .sql
1905 .execute(
1906 "UPDATE msgs
1907 SET rfc724_mid=?, chat_id=?, from_id=?, to_id=?, timestamp=?, type=?,
1908 state=?, txt=?, txt_normalized=?, subject=?, param=?,
1909 hidden=?, mime_in_reply_to=?, mime_references=?, mime_modified=?,
1910 mime_headers=?, mime_compressed=1, location_id=?, ephemeral_timer=?,
1911 ephemeral_timestamp=?
1912 WHERE id=?;",
1913 params_slice![
1914 msg.rfc724_mid,
1915 msg.chat_id,
1916 msg.from_id,
1917 to_id,
1918 msg.timestamp_sort,
1919 msg.viewtype,
1920 msg.state,
1921 msg_text,
1922 message::normalize_text(&msg_text),
1923 &msg.subject,
1924 msg.param.to_string(),
1925 msg.hidden,
1926 msg.in_reply_to.as_deref().unwrap_or_default(),
1927 new_references,
1928 new_mime_headers.is_some(),
1929 new_mime_headers.unwrap_or_default(),
1930 location_id as i32,
1931 ephemeral_timer,
1932 ephemeral_timestamp,
1933 update_msg_id
1934 ],
1935 )
1936 .await?;
1937 msg.id = update_msg_id;
1938 } else {
1939 let raw_id = context
1940 .sql
1941 .insert(
1942 "INSERT INTO msgs (
1943 rfc724_mid,
1944 chat_id,
1945 from_id,
1946 to_id,
1947 timestamp,
1948 type,
1949 state,
1950 txt,
1951 txt_normalized,
1952 subject,
1953 param,
1954 hidden,
1955 mime_in_reply_to,
1956 mime_references,
1957 mime_modified,
1958 mime_headers,
1959 mime_compressed,
1960 location_id,
1961 ephemeral_timer,
1962 ephemeral_timestamp)
1963 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,?,?,?);",
1964 params_slice![
1965 msg.rfc724_mid,
1966 msg.chat_id,
1967 msg.from_id,
1968 to_id,
1969 msg.timestamp_sort,
1970 msg.viewtype,
1971 msg.state,
1972 msg_text,
1973 message::normalize_text(&msg_text),
1974 &msg.subject,
1975 msg.param.to_string(),
1976 msg.hidden,
1977 msg.in_reply_to.as_deref().unwrap_or_default(),
1978 new_references,
1979 new_mime_headers.is_some(),
1980 new_mime_headers.unwrap_or_default(),
1981 location_id as i32,
1982 ephemeral_timer,
1983 ephemeral_timestamp
1984 ],
1985 )
1986 .await?;
1987 context.new_msgs_notify.notify_one();
1988 msg.id = MsgId::new(u32::try_from(raw_id)?);
1989
1990 maybe_set_logging_xdc(context, msg, self.id).await?;
1991 context
1992 .update_webxdc_integration_database(msg, context)
1993 .await?;
1994 }
1995 context.scheduler.interrupt_ephemeral_task().await;
1996 Ok(())
1997 }
1998
1999 pub(crate) async fn sync_contacts(&self, context: &Context) -> Result<()> {
2001 if self.is_encrypted(context).await? {
2002 let self_fp = self_fingerprint(context).await?;
2003 let fingerprint_addrs = context
2004 .sql
2005 .query_map_vec(
2006 "SELECT c.id, c.fingerprint, c.addr
2007 FROM contacts c INNER JOIN chats_contacts cc
2008 ON c.id=cc.contact_id
2009 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
2010 (self.id,),
2011 |row| {
2012 if row.get::<_, ContactId>(0)? == ContactId::SELF {
2013 return Ok((self_fp.to_string(), String::new()));
2014 }
2015 let fingerprint = row.get(1)?;
2016 let addr = row.get(2)?;
2017 Ok((fingerprint, addr))
2018 },
2019 )
2020 .await?;
2021 self.sync(context, SyncAction::SetPgpContacts(fingerprint_addrs))
2022 .await?;
2023 } else {
2024 let addrs = context
2025 .sql
2026 .query_map_vec(
2027 "SELECT c.addr \
2028 FROM contacts c INNER JOIN chats_contacts cc \
2029 ON c.id=cc.contact_id \
2030 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
2031 (self.id,),
2032 |row| row.get::<_, String>(0),
2033 )
2034 .await?;
2035 self.sync(context, SyncAction::SetContacts(addrs)).await?;
2036 }
2037 Ok(())
2038 }
2039
2040 async fn get_sync_id(&self, context: &Context) -> Result<Option<SyncId>> {
2042 match self.typ {
2043 Chattype::Single => {
2044 if self.is_device_talk() {
2045 return Ok(Some(SyncId::Device));
2046 }
2047
2048 let mut r = None;
2049 for contact_id in get_chat_contacts(context, self.id).await? {
2050 if contact_id == ContactId::SELF && !self.is_self_talk() {
2051 continue;
2052 }
2053 if r.is_some() {
2054 return Ok(None);
2055 }
2056 let contact = Contact::get_by_id(context, contact_id).await?;
2057 if let Some(fingerprint) = contact.fingerprint() {
2058 r = Some(SyncId::ContactFingerprint(fingerprint.hex()));
2059 } else {
2060 r = Some(SyncId::ContactAddr(contact.get_addr().to_string()));
2061 }
2062 }
2063 Ok(r)
2064 }
2065 Chattype::OutBroadcast
2066 | Chattype::InBroadcast
2067 | Chattype::Group
2068 | Chattype::Mailinglist => {
2069 if !self.grpid.is_empty() {
2070 return Ok(Some(SyncId::Grpid(self.grpid.clone())));
2071 }
2072
2073 let Some((parent_rfc724_mid, parent_in_reply_to, _)) = self
2074 .id
2075 .get_parent_mime_headers(context, MessageState::OutDelivered)
2076 .await?
2077 else {
2078 warn!(
2079 context,
2080 "Chat::get_sync_id({}): No good message identifying the chat found.",
2081 self.id
2082 );
2083 return Ok(None);
2084 };
2085 Ok(Some(SyncId::Msgids(vec![
2086 parent_in_reply_to,
2087 parent_rfc724_mid,
2088 ])))
2089 }
2090 }
2091 }
2092
2093 pub(crate) async fn sync(&self, context: &Context, action: SyncAction) -> Result<()> {
2095 if let Some(id) = self.get_sync_id(context).await? {
2096 sync(context, id, action).await?;
2097 }
2098 Ok(())
2099 }
2100}
2101
2102pub(crate) async fn sync(context: &Context, id: SyncId, action: SyncAction) -> Result<()> {
2103 context
2104 .add_sync_item(SyncData::AlterChat { id, action })
2105 .await?;
2106 context.scheduler.interrupt_inbox().await;
2107 Ok(())
2108}
2109
2110#[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize, EnumIter)]
2112#[repr(i8)]
2113pub enum ChatVisibility {
2114 Normal = 0,
2116
2117 Archived = 1,
2119
2120 Pinned = 2,
2122}
2123
2124impl rusqlite::types::ToSql for ChatVisibility {
2125 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
2126 let val = rusqlite::types::Value::Integer(*self as i64);
2127 let out = rusqlite::types::ToSqlOutput::Owned(val);
2128 Ok(out)
2129 }
2130}
2131
2132impl rusqlite::types::FromSql for ChatVisibility {
2133 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
2134 i64::column_result(value).map(|val| {
2135 match val {
2136 2 => ChatVisibility::Pinned,
2137 1 => ChatVisibility::Archived,
2138 0 => ChatVisibility::Normal,
2139 _ => ChatVisibility::Normal,
2141 }
2142 })
2143 }
2144}
2145
2146#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2148#[non_exhaustive]
2149pub struct ChatInfo {
2150 pub id: ChatId,
2152
2153 #[serde(rename = "type")]
2160 pub type_: u32,
2161
2162 pub name: String,
2164
2165 pub archived: bool,
2167
2168 pub param: String,
2172
2173 pub is_sending_locations: bool,
2175
2176 pub color: u32,
2180
2181 pub profile_image: std::path::PathBuf,
2186
2187 pub draft: String,
2195
2196 pub is_muted: bool,
2200
2201 pub ephemeral_timer: EphemeralTimer,
2203 }
2209
2210async fn get_asset_icon(context: &Context, name: &str, bytes: &[u8]) -> Result<PathBuf> {
2211 ensure!(name.starts_with("icon-"));
2212 if let Some(icon) = context.sql.get_raw_config(name).await? {
2213 return Ok(get_abs_path(context, Path::new(&icon)));
2214 }
2215
2216 let blob =
2217 BlobObject::create_and_deduplicate_from_bytes(context, bytes, &format!("{name}.png"))?;
2218 let icon = blob.as_name().to_string();
2219 context.sql.set_raw_config(name, Some(&icon)).await?;
2220
2221 Ok(get_abs_path(context, Path::new(&icon)))
2222}
2223
2224pub(crate) async fn get_saved_messages_icon(context: &Context) -> Result<PathBuf> {
2225 get_asset_icon(
2226 context,
2227 "icon-saved-messages",
2228 include_bytes!("../assets/icon-saved-messages.png"),
2229 )
2230 .await
2231}
2232
2233pub(crate) async fn get_device_icon(context: &Context) -> Result<PathBuf> {
2234 get_asset_icon(
2235 context,
2236 "icon-device",
2237 include_bytes!("../assets/icon-device.png"),
2238 )
2239 .await
2240}
2241
2242pub(crate) async fn get_archive_icon(context: &Context) -> Result<PathBuf> {
2243 get_asset_icon(
2244 context,
2245 "icon-archive",
2246 include_bytes!("../assets/icon-archive.png"),
2247 )
2248 .await
2249}
2250
2251pub(crate) async fn get_unencrypted_icon(context: &Context) -> Result<PathBuf> {
2254 get_asset_icon(
2255 context,
2256 "icon-unencrypted",
2257 include_bytes!("../assets/icon-unencrypted.png"),
2258 )
2259 .await
2260}
2261
2262async fn update_special_chat_name(
2263 context: &Context,
2264 contact_id: ContactId,
2265 name: String,
2266) -> Result<()> {
2267 if let Some(ChatIdBlocked { id: chat_id, .. }) =
2268 ChatIdBlocked::lookup_by_contact(context, contact_id).await?
2269 {
2270 context
2272 .sql
2273 .execute(
2274 "UPDATE chats SET name=? WHERE id=? AND name!=?",
2275 (&name, chat_id, &name),
2276 )
2277 .await?;
2278 }
2279 Ok(())
2280}
2281
2282pub(crate) async fn update_special_chat_names(context: &Context) -> Result<()> {
2283 update_special_chat_name(
2284 context,
2285 ContactId::DEVICE,
2286 stock_str::device_messages(context).await,
2287 )
2288 .await?;
2289 update_special_chat_name(
2290 context,
2291 ContactId::SELF,
2292 stock_str::saved_messages(context).await,
2293 )
2294 .await?;
2295 Ok(())
2296}
2297
2298#[derive(Debug)]
2306pub(crate) struct ChatIdBlocked {
2307 pub id: ChatId,
2309
2310 pub blocked: Blocked,
2312}
2313
2314impl ChatIdBlocked {
2315 pub async fn lookup_by_contact(
2319 context: &Context,
2320 contact_id: ContactId,
2321 ) -> Result<Option<Self>> {
2322 ensure!(context.sql.is_open().await, "Database not available");
2323 ensure!(
2324 contact_id != ContactId::UNDEFINED,
2325 "Invalid contact id requested"
2326 );
2327
2328 context
2329 .sql
2330 .query_row_optional(
2331 "SELECT c.id, c.blocked
2332 FROM chats c
2333 INNER JOIN chats_contacts j
2334 ON c.id=j.chat_id
2335 WHERE c.type=100 -- 100 = Chattype::Single
2336 AND c.id>9 -- 9 = DC_CHAT_ID_LAST_SPECIAL
2337 AND j.contact_id=?;",
2338 (contact_id,),
2339 |row| {
2340 let id: ChatId = row.get(0)?;
2341 let blocked: Blocked = row.get(1)?;
2342 Ok(ChatIdBlocked { id, blocked })
2343 },
2344 )
2345 .await
2346 }
2347
2348 pub async fn get_for_contact(
2353 context: &Context,
2354 contact_id: ContactId,
2355 create_blocked: Blocked,
2356 ) -> Result<Self> {
2357 ensure!(context.sql.is_open().await, "Database not available");
2358 ensure!(
2359 contact_id != ContactId::UNDEFINED,
2360 "Invalid contact id requested"
2361 );
2362
2363 if let Some(res) = Self::lookup_by_contact(context, contact_id).await? {
2364 return Ok(res);
2366 }
2367
2368 let contact = Contact::get_by_id(context, contact_id).await?;
2369 let chat_name = contact.get_display_name().to_string();
2370 let mut params = Params::new();
2371 match contact_id {
2372 ContactId::SELF => {
2373 params.set_int(Param::Selftalk, 1);
2374 }
2375 ContactId::DEVICE => {
2376 params.set_int(Param::Devicetalk, 1);
2377 }
2378 _ => (),
2379 }
2380
2381 let smeared_time = create_smeared_timestamp(context);
2382
2383 let chat_id = context
2384 .sql
2385 .transaction(move |transaction| {
2386 transaction.execute(
2387 "INSERT INTO chats
2388 (type, name, param, blocked, created_timestamp)
2389 VALUES(?, ?, ?, ?, ?)",
2390 (
2391 Chattype::Single,
2392 chat_name,
2393 params.to_string(),
2394 create_blocked as u8,
2395 smeared_time,
2396 ),
2397 )?;
2398 let chat_id = ChatId::new(
2399 transaction
2400 .last_insert_rowid()
2401 .try_into()
2402 .context("chat table rowid overflows u32")?,
2403 );
2404
2405 transaction.execute(
2406 "INSERT INTO chats_contacts
2407 (chat_id, contact_id)
2408 VALUES((SELECT last_insert_rowid()), ?)",
2409 (contact_id,),
2410 )?;
2411
2412 Ok(chat_id)
2413 })
2414 .await?;
2415
2416 let chat = Chat::load_from_db(context, chat_id).await?;
2417 if chat.is_encrypted(context).await?
2418 && !chat.param.exists(Param::Devicetalk)
2419 && !chat.param.exists(Param::Selftalk)
2420 {
2421 chat_id.add_encrypted_msg(context, smeared_time).await?;
2422 }
2423
2424 Ok(Self {
2425 id: chat_id,
2426 blocked: create_blocked,
2427 })
2428 }
2429}
2430
2431async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
2432 if msg.viewtype == Viewtype::Text || msg.viewtype == Viewtype::Call {
2433 } else if msg.viewtype.has_file() {
2435 let viewtype_orig = msg.viewtype;
2436 let mut blob = msg
2437 .param
2438 .get_file_blob(context)?
2439 .with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?;
2440 let mut maybe_image = false;
2441
2442 if msg.viewtype == Viewtype::File
2443 || msg.viewtype == Viewtype::Image
2444 || msg.viewtype == Viewtype::Sticker && !msg.param.exists(Param::ForceSticker)
2445 {
2446 if let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg) {
2453 if msg.viewtype == Viewtype::Sticker {
2454 if better_type != Viewtype::Image {
2455 msg.param.set_int(Param::ForceSticker, 1);
2457 }
2458 } else if better_type == Viewtype::Image {
2459 maybe_image = true;
2460 } else if better_type != Viewtype::Webxdc
2461 || context
2462 .ensure_sendable_webxdc_file(&blob.to_abs_path())
2463 .await
2464 .is_ok()
2465 {
2466 msg.viewtype = better_type;
2467 }
2468 }
2469 } else if msg.viewtype == Viewtype::Webxdc {
2470 context
2471 .ensure_sendable_webxdc_file(&blob.to_abs_path())
2472 .await?;
2473 }
2474
2475 if msg.viewtype == Viewtype::Vcard {
2476 msg.try_set_vcard(context, &blob.to_abs_path()).await?;
2477 }
2478 if msg.viewtype == Viewtype::File && maybe_image
2479 || msg.viewtype == Viewtype::Image
2480 || msg.viewtype == Viewtype::Sticker && !msg.param.exists(Param::ForceSticker)
2481 {
2482 let new_name = blob
2483 .check_or_recode_image(context, msg.get_filename(), &mut msg.viewtype)
2484 .await?;
2485 msg.param.set(Param::Filename, new_name);
2486 msg.param.set(Param::File, blob.as_name());
2487 }
2488
2489 if !msg.param.exists(Param::MimeType) {
2490 if let Some((viewtype, mime)) = message::guess_msgtype_from_suffix(msg) {
2491 let mime = match viewtype != Viewtype::Image
2494 || matches!(msg.viewtype, Viewtype::Image | Viewtype::Sticker)
2495 {
2496 true => mime,
2497 false => "application/octet-stream",
2498 };
2499 msg.param.set(Param::MimeType, mime);
2500 }
2501 }
2502
2503 msg.try_calc_and_set_dimensions(context).await?;
2504
2505 let filename = msg.get_filename().context("msg has no file")?;
2506 let suffix = Path::new(&filename)
2507 .extension()
2508 .and_then(|e| e.to_str())
2509 .unwrap_or("dat");
2510 let filename: String = match viewtype_orig {
2514 Viewtype::Voice => format!(
2515 "voice-messsage_{}.{}",
2516 chrono::Utc
2517 .timestamp_opt(msg.timestamp_sort, 0)
2518 .single()
2519 .map_or_else(
2520 || "YY-mm-dd_hh:mm:ss".to_string(),
2521 |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string()
2522 ),
2523 &suffix
2524 ),
2525 Viewtype::Image | Viewtype::Gif => format!(
2526 "image_{}.{}",
2527 chrono::Utc
2528 .timestamp_opt(msg.timestamp_sort, 0)
2529 .single()
2530 .map_or_else(
2531 || "YY-mm-dd_hh:mm:ss".to_string(),
2532 |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string(),
2533 ),
2534 &suffix,
2535 ),
2536 Viewtype::Video => format!(
2537 "video_{}.{}",
2538 chrono::Utc
2539 .timestamp_opt(msg.timestamp_sort, 0)
2540 .single()
2541 .map_or_else(
2542 || "YY-mm-dd_hh:mm:ss".to_string(),
2543 |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string()
2544 ),
2545 &suffix
2546 ),
2547 _ => filename,
2548 };
2549 msg.param.set(Param::Filename, filename);
2550
2551 info!(
2552 context,
2553 "Attaching \"{}\" for message type #{}.",
2554 blob.to_abs_path().display(),
2555 msg.viewtype
2556 );
2557 } else {
2558 bail!("Cannot send messages of type #{}.", msg.viewtype);
2559 }
2560 Ok(())
2561}
2562
2563pub async fn is_contact_in_chat(
2565 context: &Context,
2566 chat_id: ChatId,
2567 contact_id: ContactId,
2568) -> Result<bool> {
2569 let exists = context
2575 .sql
2576 .exists(
2577 "SELECT COUNT(*) FROM chats_contacts
2578 WHERE chat_id=? AND contact_id=?
2579 AND add_timestamp >= remove_timestamp",
2580 (chat_id, contact_id),
2581 )
2582 .await?;
2583 Ok(exists)
2584}
2585
2586pub async fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2593 ensure!(
2594 !chat_id.is_special(),
2595 "chat_id cannot be a special chat: {chat_id}"
2596 );
2597
2598 if msg.state != MessageState::Undefined && msg.state != MessageState::OutPreparing {
2599 msg.param.remove(Param::GuaranteeE2ee);
2600 msg.param.remove(Param::ForcePlaintext);
2601 msg.update_param(context).await?;
2602 }
2603
2604 if msg.is_system_message() {
2606 msg.text = sanitize_bidi_characters(&msg.text);
2607 }
2608
2609 if !prepare_send_msg(context, chat_id, msg).await?.is_empty() {
2610 if !msg.hidden {
2611 context.emit_msgs_changed(msg.chat_id, msg.id);
2612 }
2613
2614 if msg.param.exists(Param::SetLatitude) {
2615 context.emit_location_changed(Some(ContactId::SELF)).await?;
2616 }
2617
2618 context.scheduler.interrupt_smtp().await;
2619 }
2620
2621 Ok(msg.id)
2622}
2623
2624pub async fn send_msg_sync(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2629 let rowids = prepare_send_msg(context, chat_id, msg).await?;
2630 if rowids.is_empty() {
2631 return Ok(msg.id);
2632 }
2633 let mut smtp = crate::smtp::Smtp::new();
2634 for rowid in rowids {
2635 send_msg_to_smtp(context, &mut smtp, rowid)
2636 .await
2637 .context("failed to send message, queued for later sending")?;
2638 }
2639 context.emit_msgs_changed(msg.chat_id, msg.id);
2640 Ok(msg.id)
2641}
2642
2643async fn prepare_send_msg(
2647 context: &Context,
2648 chat_id: ChatId,
2649 msg: &mut Message,
2650) -> Result<Vec<i64>> {
2651 let mut chat = Chat::load_from_db(context, chat_id).await?;
2652
2653 let skip_fn = |reason: &CantSendReason| match reason {
2654 CantSendReason::ContactRequest => {
2655 msg.param.get_cmd() == SystemMessage::SecurejoinMessage
2658 }
2659 CantSendReason::NotAMember | CantSendReason::InBroadcast => {
2663 msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup
2664 }
2665 CantSendReason::MissingKey => msg
2666 .param
2667 .get_bool(Param::ForcePlaintext)
2668 .unwrap_or_default(),
2669 _ => false,
2670 };
2671 if let Some(reason) = chat.why_cant_send_ex(context, &skip_fn).await? {
2672 bail!("Cannot send to {chat_id}: {reason}");
2673 }
2674
2675 if chat.typ != Chattype::Single && !context.get_config_bool(Config::Bot).await? {
2680 if let Some(quoted_message) = msg.quoted_message(context).await? {
2681 if quoted_message.chat_id != chat_id {
2682 bail!(
2683 "Quote of message from {} cannot be sent to {chat_id}",
2684 quoted_message.chat_id
2685 );
2686 }
2687 }
2688 }
2689
2690 let update_msg_id = if msg.state == MessageState::OutDraft {
2692 msg.hidden = false;
2693 if !msg.id.is_special() && msg.chat_id == chat_id {
2694 Some(msg.id)
2695 } else {
2696 None
2697 }
2698 } else {
2699 None
2700 };
2701
2702 msg.state = MessageState::OutPending;
2704
2705 msg.timestamp_sort = create_smeared_timestamp(context);
2706 prepare_msg_blob(context, msg).await?;
2707 if !msg.hidden {
2708 chat_id.unarchive_if_not_muted(context, msg.state).await?;
2709 }
2710 chat.prepare_msg_raw(context, msg, update_msg_id).await?;
2711
2712 let row_ids = create_send_msg_jobs(context, msg)
2713 .await
2714 .context("Failed to create send jobs")?;
2715 if !row_ids.is_empty() {
2716 donation_request_maybe(context).await.log_err(context).ok();
2717 }
2718 Ok(row_ids)
2719}
2720
2721pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -> Result<Vec<i64>> {
2731 if msg.param.get_cmd() == SystemMessage::GroupNameChanged {
2732 msg.chat_id
2733 .update_timestamp(context, Param::GroupNameTimestamp, msg.timestamp_sort)
2734 .await?;
2735 }
2736
2737 let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
2738 let mimefactory = match MimeFactory::from_msg(context, msg.clone()).await {
2739 Ok(mf) => mf,
2740 Err(err) => {
2741 message::set_msg_failed(context, msg, &err.to_string())
2743 .await
2744 .ok();
2745 return Err(err);
2746 }
2747 };
2748 let attach_selfavatar = mimefactory.attach_selfavatar;
2749 let mut recipients = mimefactory.recipients();
2750
2751 let from = context.get_primary_self_addr().await?;
2752 let lowercase_from = from.to_lowercase();
2753
2754 recipients.retain(|x| x.to_lowercase() != lowercase_from);
2767 if (context.get_config_bool(Config::BccSelf).await?
2768 || msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage)
2769 && (context.get_config_delete_server_after().await? != Some(0) || !recipients.is_empty())
2770 {
2771 recipients.push(from);
2772 }
2773
2774 if msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden {
2776 recipients.clear();
2777 }
2778
2779 if recipients.is_empty() {
2780 info!(
2782 context,
2783 "Message {} has no recipient, skipping smtp-send.", msg.id
2784 );
2785 msg.param.set_int(Param::GuaranteeE2ee, 1);
2786 msg.update_param(context).await?;
2787 msg.id.set_delivered(context).await?;
2788 msg.state = MessageState::OutDelivered;
2789 return Ok(Vec::new());
2790 }
2791
2792 let rendered_msg = match mimefactory.render(context).await {
2793 Ok(res) => Ok(res),
2794 Err(err) => {
2795 message::set_msg_failed(context, msg, &err.to_string()).await?;
2796 Err(err)
2797 }
2798 }?;
2799
2800 if needs_encryption && !rendered_msg.is_encrypted {
2801 message::set_msg_failed(
2803 context,
2804 msg,
2805 "End-to-end-encryption unavailable unexpectedly.",
2806 )
2807 .await?;
2808 bail!(
2809 "e2e encryption unavailable {} - {:?}",
2810 msg.id,
2811 needs_encryption
2812 );
2813 }
2814
2815 let now = smeared_time(context);
2816
2817 if rendered_msg.last_added_location_id.is_some() {
2818 if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, now).await {
2819 error!(context, "Failed to set kml sent_timestamp: {err:#}.");
2820 }
2821 }
2822
2823 if attach_selfavatar {
2824 if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, now).await {
2825 error!(context, "Failed to set selfavatar timestamp: {err:#}.");
2826 }
2827 }
2828
2829 if rendered_msg.is_encrypted {
2830 msg.param.set_int(Param::GuaranteeE2ee, 1);
2831 } else {
2832 msg.param.remove(Param::GuaranteeE2ee);
2833 }
2834 msg.subject.clone_from(&rendered_msg.subject);
2835 context
2836 .sql
2837 .execute(
2838 "UPDATE msgs SET subject=?, param=? WHERE id=?",
2839 (&msg.subject, msg.param.to_string(), msg.id),
2840 )
2841 .await?;
2842
2843 let chunk_size = context.get_max_smtp_rcpt_to().await?;
2844 let trans_fn = |t: &mut rusqlite::Transaction| {
2845 let mut row_ids = Vec::<i64>::new();
2846 if let Some(sync_ids) = rendered_msg.sync_ids_to_delete {
2847 t.execute(
2848 &format!("DELETE FROM multi_device_sync WHERE id IN ({sync_ids})"),
2849 (),
2850 )?;
2851 t.execute(
2852 "INSERT INTO imap_send (mime, msg_id) VALUES (?, ?)",
2853 (&rendered_msg.message, msg.id),
2854 )?;
2855 } else {
2856 for recipients_chunk in recipients.chunks(chunk_size) {
2857 let recipients_chunk = recipients_chunk.join(" ");
2858 let row_id = t.execute(
2859 "INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id) \
2860 VALUES (?1, ?2, ?3, ?4)",
2861 (
2862 &rendered_msg.rfc724_mid,
2863 recipients_chunk,
2864 &rendered_msg.message,
2865 msg.id,
2866 ),
2867 )?;
2868 row_ids.push(row_id.try_into()?);
2869 }
2870 }
2871 Ok(row_ids)
2872 };
2873 context.sql.transaction(trans_fn).await
2874}
2875
2876pub async fn send_text_msg(
2880 context: &Context,
2881 chat_id: ChatId,
2882 text_to_send: String,
2883) -> Result<MsgId> {
2884 ensure!(
2885 !chat_id.is_special(),
2886 "bad chat_id, can not be a special chat: {chat_id}"
2887 );
2888
2889 let mut msg = Message::new_text(text_to_send);
2890 send_msg(context, chat_id, &mut msg).await
2891}
2892
2893pub async fn send_edit_request(context: &Context, msg_id: MsgId, new_text: String) -> Result<()> {
2895 let mut original_msg = Message::load_from_db(context, msg_id).await?;
2896 ensure!(
2897 original_msg.from_id == ContactId::SELF,
2898 "Can edit only own messages"
2899 );
2900 ensure!(!original_msg.is_info(), "Cannot edit info messages");
2901 ensure!(!original_msg.has_html(), "Cannot edit HTML messages");
2902 ensure!(original_msg.viewtype != Viewtype::Call, "Cannot edit calls");
2903 ensure!(
2904 !original_msg.text.is_empty(), "Cannot add text"
2906 );
2907 ensure!(!new_text.trim().is_empty(), "Edited text cannot be empty");
2908 if original_msg.text == new_text {
2909 info!(context, "Text unchanged.");
2910 return Ok(());
2911 }
2912
2913 save_text_edit_to_db(context, &mut original_msg, &new_text).await?;
2914
2915 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() {
2918 edit_msg.param.set_int(Param::GuaranteeE2ee, 1);
2919 }
2920 edit_msg
2921 .param
2922 .set(Param::TextEditFor, original_msg.rfc724_mid);
2923 edit_msg.hidden = true;
2924 send_msg(context, original_msg.chat_id, &mut edit_msg).await?;
2925 Ok(())
2926}
2927
2928pub(crate) async fn save_text_edit_to_db(
2929 context: &Context,
2930 original_msg: &mut Message,
2931 new_text: &str,
2932) -> Result<()> {
2933 original_msg.param.set_int(Param::IsEdited, 1);
2934 context
2935 .sql
2936 .execute(
2937 "UPDATE msgs SET txt=?, txt_normalized=?, param=? WHERE id=?",
2938 (
2939 new_text,
2940 message::normalize_text(new_text),
2941 original_msg.param.to_string(),
2942 original_msg.id,
2943 ),
2944 )
2945 .await?;
2946 context.emit_msgs_changed(original_msg.chat_id, original_msg.id);
2947 Ok(())
2948}
2949
2950async fn donation_request_maybe(context: &Context) -> Result<()> {
2951 let secs_between_checks = 30 * 24 * 60 * 60;
2952 let now = time();
2953 let ts = context
2954 .get_config_i64(Config::DonationRequestNextCheck)
2955 .await?;
2956 if ts > now {
2957 return Ok(());
2958 }
2959 let msg_cnt = context.sql.count(
2960 "SELECT COUNT(*) FROM msgs WHERE state>=? AND hidden=0",
2961 (MessageState::OutDelivered,),
2962 );
2963 let ts = if ts == 0 || msg_cnt.await? < 100 {
2964 now.saturating_add(secs_between_checks)
2965 } else {
2966 let mut msg = Message::new_text(stock_str::donation_request(context).await);
2967 add_device_msg(context, None, Some(&mut msg)).await?;
2968 i64::MAX
2969 };
2970 context
2971 .set_config_internal(Config::DonationRequestNextCheck, Some(&ts.to_string()))
2972 .await
2973}
2974
2975#[derive(Debug)]
2977pub struct MessageListOptions {
2978 pub info_only: bool,
2980
2981 pub add_daymarker: bool,
2983}
2984
2985pub async fn get_chat_msgs(context: &Context, chat_id: ChatId) -> Result<Vec<ChatItem>> {
2987 get_chat_msgs_ex(
2988 context,
2989 chat_id,
2990 MessageListOptions {
2991 info_only: false,
2992 add_daymarker: false,
2993 },
2994 )
2995 .await
2996}
2997
2998pub async fn get_chat_msgs_ex(
3000 context: &Context,
3001 chat_id: ChatId,
3002 options: MessageListOptions,
3003) -> Result<Vec<ChatItem>> {
3004 let MessageListOptions {
3005 info_only,
3006 add_daymarker,
3007 } = options;
3008 let process_row = if info_only {
3009 |row: &rusqlite::Row| {
3010 let params = row.get::<_, String>("param")?;
3012 let (from_id, to_id) = (
3013 row.get::<_, ContactId>("from_id")?,
3014 row.get::<_, ContactId>("to_id")?,
3015 );
3016 let is_info_msg: bool = from_id == ContactId::INFO
3017 || to_id == ContactId::INFO
3018 || match Params::from_str(¶ms) {
3019 Ok(p) => {
3020 let cmd = p.get_cmd();
3021 cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
3022 }
3023 _ => false,
3024 };
3025
3026 Ok((
3027 row.get::<_, i64>("timestamp")?,
3028 row.get::<_, MsgId>("id")?,
3029 !is_info_msg,
3030 ))
3031 }
3032 } else {
3033 |row: &rusqlite::Row| {
3034 Ok((
3035 row.get::<_, i64>("timestamp")?,
3036 row.get::<_, MsgId>("id")?,
3037 false,
3038 ))
3039 }
3040 };
3041 let process_rows = |rows: rusqlite::MappedRows<_>| {
3042 let mut sorted_rows = Vec::new();
3045 for row in rows {
3046 let (ts, curr_id, exclude_message): (i64, MsgId, bool) = row?;
3047 if !exclude_message {
3048 sorted_rows.push((ts, curr_id));
3049 }
3050 }
3051 sorted_rows.sort_unstable();
3052
3053 let mut ret = Vec::new();
3054 let mut last_day = 0;
3055 let cnv_to_local = gm2local_offset();
3056
3057 for (ts, curr_id) in sorted_rows {
3058 if add_daymarker {
3059 let curr_local_timestamp = ts + cnv_to_local;
3060 let secs_in_day = 86400;
3061 let curr_day = curr_local_timestamp / secs_in_day;
3062 if curr_day != last_day {
3063 ret.push(ChatItem::DayMarker {
3064 timestamp: curr_day * secs_in_day - cnv_to_local,
3065 });
3066 last_day = curr_day;
3067 }
3068 }
3069 ret.push(ChatItem::Message { msg_id: curr_id });
3070 }
3071 Ok(ret)
3072 };
3073
3074 let items = if info_only {
3075 context
3076 .sql
3077 .query_map(
3078 "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
3080 FROM msgs m
3081 WHERE m.chat_id=?
3082 AND m.hidden=0
3083 AND (
3084 m.param GLOB \"*S=*\"
3085 OR m.from_id == ?
3086 OR m.to_id == ?
3087 );",
3088 (chat_id, ContactId::INFO, ContactId::INFO),
3089 process_row,
3090 process_rows,
3091 )
3092 .await?
3093 } else {
3094 context
3095 .sql
3096 .query_map(
3097 "SELECT m.id AS id, m.timestamp AS timestamp
3098 FROM msgs m
3099 WHERE m.chat_id=?
3100 AND m.hidden=0;",
3101 (chat_id,),
3102 process_row,
3103 process_rows,
3104 )
3105 .await?
3106 };
3107 Ok(items)
3108}
3109
3110pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()> {
3113 if chat_id.is_archived_link() {
3116 let chat_ids_in_archive = context
3117 .sql
3118 .query_map_vec(
3119 "SELECT DISTINCT(m.chat_id) FROM msgs m
3120 LEFT JOIN chats c ON m.chat_id=c.id
3121 WHERE m.state=10 AND m.hidden=0 AND m.chat_id>9 AND c.archived=1",
3122 (),
3123 |row| row.get::<_, ChatId>(0),
3124 )
3125 .await?;
3126 if chat_ids_in_archive.is_empty() {
3127 return Ok(());
3128 }
3129
3130 context
3131 .sql
3132 .transaction(|transaction| {
3133 let mut stmt = transaction.prepare(
3134 "UPDATE msgs SET state=13 WHERE state=10 AND hidden=0 AND chat_id = ?",
3135 )?;
3136 for chat_id_in_archive in &chat_ids_in_archive {
3137 stmt.execute((chat_id_in_archive,))?;
3138 }
3139 Ok(())
3140 })
3141 .await?;
3142
3143 for chat_id_in_archive in chat_ids_in_archive {
3144 start_chat_ephemeral_timers(context, chat_id_in_archive).await?;
3145 context.emit_event(EventType::MsgsNoticed(chat_id_in_archive));
3146 chatlist_events::emit_chatlist_item_changed(context, chat_id_in_archive);
3147 }
3148 } else {
3149 start_chat_ephemeral_timers(context, chat_id).await?;
3150
3151 let noticed_msgs_count = context
3152 .sql
3153 .execute(
3154 "UPDATE msgs
3155 SET state=?
3156 WHERE state=?
3157 AND hidden=0
3158 AND chat_id=?;",
3159 (MessageState::InNoticed, MessageState::InFresh, chat_id),
3160 )
3161 .await?;
3162
3163 let hidden_messages = context
3166 .sql
3167 .query_map_vec(
3168 "SELECT id, rfc724_mid FROM msgs
3169 WHERE state=?
3170 AND hidden=1
3171 AND chat_id=?
3172 ORDER BY id LIMIT 100", (MessageState::InFresh, chat_id), |row| {
3175 let msg_id: MsgId = row.get(0)?;
3176 let rfc724_mid: String = row.get(1)?;
3177 Ok((msg_id, rfc724_mid))
3178 },
3179 )
3180 .await?;
3181 for (msg_id, rfc724_mid) in &hidden_messages {
3182 message::update_msg_state(context, *msg_id, MessageState::InSeen).await?;
3183 imap::markseen_on_imap_table(context, rfc724_mid).await?;
3184 }
3185
3186 if noticed_msgs_count == 0 {
3187 return Ok(());
3188 }
3189 }
3190
3191 context.emit_event(EventType::MsgsNoticed(chat_id));
3192 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3193 context.on_archived_chats_maybe_noticed();
3194 Ok(())
3195}
3196
3197pub(crate) async fn mark_old_messages_as_noticed(
3204 context: &Context,
3205 mut msgs: Vec<ReceivedMsg>,
3206) -> Result<()> {
3207 msgs.retain(|m| m.state.is_outgoing());
3208 if msgs.is_empty() {
3209 return Ok(());
3210 }
3211
3212 let mut msgs_by_chat: HashMap<ChatId, ReceivedMsg> = HashMap::new();
3213 for msg in msgs {
3214 let chat_id = msg.chat_id;
3215 if let Some(existing_msg) = msgs_by_chat.get(&chat_id) {
3216 if msg.sort_timestamp > existing_msg.sort_timestamp {
3217 msgs_by_chat.insert(chat_id, msg);
3218 }
3219 } else {
3220 msgs_by_chat.insert(chat_id, msg);
3221 }
3222 }
3223
3224 let changed_chats = context
3225 .sql
3226 .transaction(|transaction| {
3227 let mut changed_chats = Vec::new();
3228 for (_, msg) in msgs_by_chat {
3229 let changed_rows = transaction.execute(
3230 "UPDATE msgs
3231 SET state=?
3232 WHERE state=?
3233 AND hidden=0
3234 AND chat_id=?
3235 AND timestamp<=?;",
3236 (
3237 MessageState::InNoticed,
3238 MessageState::InFresh,
3239 msg.chat_id,
3240 msg.sort_timestamp,
3241 ),
3242 )?;
3243 if changed_rows > 0 {
3244 changed_chats.push(msg.chat_id);
3245 }
3246 }
3247 Ok(changed_chats)
3248 })
3249 .await?;
3250
3251 if !changed_chats.is_empty() {
3252 info!(
3253 context,
3254 "Marking chats as noticed because there are newer outgoing messages: {changed_chats:?}."
3255 );
3256 context.on_archived_chats_maybe_noticed();
3257 }
3258
3259 for c in changed_chats {
3260 start_chat_ephemeral_timers(context, c).await?;
3261 context.emit_event(EventType::MsgsNoticed(c));
3262 chatlist_events::emit_chatlist_item_changed(context, c);
3263 }
3264
3265 Ok(())
3266}
3267
3268pub async fn get_chat_media(
3275 context: &Context,
3276 chat_id: Option<ChatId>,
3277 msg_type: Viewtype,
3278 msg_type2: Viewtype,
3279 msg_type3: Viewtype,
3280) -> Result<Vec<MsgId>> {
3281 let list = if msg_type == Viewtype::Webxdc
3282 && msg_type2 == Viewtype::Unknown
3283 && msg_type3 == Viewtype::Unknown
3284 {
3285 context
3286 .sql
3287 .query_map_vec(
3288 "SELECT id
3289 FROM msgs
3290 WHERE (1=? OR chat_id=?)
3291 AND chat_id != ?
3292 AND type = ?
3293 AND hidden=0
3294 ORDER BY max(timestamp, timestamp_rcvd), id;",
3295 (
3296 chat_id.is_none(),
3297 chat_id.unwrap_or_else(|| ChatId::new(0)),
3298 DC_CHAT_ID_TRASH,
3299 Viewtype::Webxdc,
3300 ),
3301 |row| row.get::<_, MsgId>(0),
3302 )
3303 .await?
3304 } else {
3305 context
3306 .sql
3307 .query_map_vec(
3308 "SELECT id
3309 FROM msgs
3310 WHERE (1=? OR chat_id=?)
3311 AND chat_id != ?
3312 AND type IN (?, ?, ?)
3313 AND hidden=0
3314 ORDER BY timestamp, id;",
3315 (
3316 chat_id.is_none(),
3317 chat_id.unwrap_or_else(|| ChatId::new(0)),
3318 DC_CHAT_ID_TRASH,
3319 msg_type,
3320 if msg_type2 != Viewtype::Unknown {
3321 msg_type2
3322 } else {
3323 msg_type
3324 },
3325 if msg_type3 != Viewtype::Unknown {
3326 msg_type3
3327 } else {
3328 msg_type
3329 },
3330 ),
3331 |row| row.get::<_, MsgId>(0),
3332 )
3333 .await?
3334 };
3335 Ok(list)
3336}
3337
3338pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3340 context
3343 .sql
3344 .query_map_vec(
3345 "SELECT cc.contact_id
3346 FROM chats_contacts cc
3347 LEFT JOIN contacts c
3348 ON c.id=cc.contact_id
3349 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp
3350 ORDER BY c.id=1, c.last_seen DESC, c.id DESC;",
3351 (chat_id,),
3352 |row| row.get::<_, ContactId>(0),
3353 )
3354 .await
3355}
3356
3357pub async fn get_past_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3361 let now = time();
3362 context
3363 .sql
3364 .query_map_vec(
3365 "SELECT cc.contact_id
3366 FROM chats_contacts cc
3367 LEFT JOIN contacts c
3368 ON c.id=cc.contact_id
3369 WHERE cc.chat_id=?
3370 AND cc.add_timestamp < cc.remove_timestamp
3371 AND ? < cc.remove_timestamp
3372 ORDER BY c.id=1, cc.remove_timestamp DESC, c.id DESC",
3373 (chat_id, now.saturating_sub(60 * 24 * 3600)),
3374 |row| row.get::<_, ContactId>(0),
3375 )
3376 .await
3377}
3378
3379pub async fn create_group(context: &Context, name: &str) -> Result<ChatId> {
3381 create_group_ex(context, Sync, create_id(), name).await
3382}
3383
3384pub async fn create_group_unencrypted(context: &Context, name: &str) -> Result<ChatId> {
3386 create_group_ex(context, Sync, String::new(), name).await
3387}
3388
3389pub(crate) async fn create_group_ex(
3396 context: &Context,
3397 sync: sync::Sync,
3398 grpid: String,
3399 name: &str,
3400) -> Result<ChatId> {
3401 let mut chat_name = sanitize_single_line(name);
3402 if chat_name.is_empty() {
3403 error!(context, "Invalid chat name: {name}.");
3406 chat_name = "…".to_string();
3407 }
3408
3409 let timestamp = create_smeared_timestamp(context);
3410 let row_id = context
3411 .sql
3412 .insert(
3413 "INSERT INTO chats
3414 (type, name, grpid, param, created_timestamp)
3415 VALUES(?, ?, ?, \'U=1\', ?);",
3416 (Chattype::Group, &chat_name, &grpid, timestamp),
3417 )
3418 .await?;
3419
3420 let chat_id = ChatId::new(u32::try_from(row_id)?);
3421 add_to_chat_contacts_table(context, timestamp, chat_id, &[ContactId::SELF]).await?;
3422
3423 context.emit_msgs_changed_without_ids();
3424 chatlist_events::emit_chatlist_changed(context);
3425 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3426
3427 if !grpid.is_empty() {
3428 chat_id.add_encrypted_msg(context, timestamp).await?;
3430 }
3431
3432 if !context.get_config_bool(Config::Bot).await?
3433 && !context.get_config_bool(Config::SkipStartMessages).await?
3434 {
3435 let text = stock_str::new_group_send_first_message(context).await;
3436 add_info_msg(context, chat_id, &text, create_smeared_timestamp(context)).await?;
3437 }
3438 if let (true, true) = (sync.into(), !grpid.is_empty()) {
3439 let id = SyncId::Grpid(grpid);
3440 let action = SyncAction::CreateGroupEncrypted(chat_name);
3441 self::sync(context, id, action).await.log_err(context).ok();
3442 }
3443 Ok(chat_id)
3444}
3445
3446pub async fn create_broadcast(context: &Context, chat_name: String) -> Result<ChatId> {
3462 let grpid = create_id();
3463 create_broadcast_ex(context, Sync, grpid, chat_name).await
3464}
3465
3466pub(crate) async fn create_broadcast_ex(
3467 context: &Context,
3468 sync: sync::Sync,
3469 grpid: String,
3470 chat_name: String,
3471) -> Result<ChatId> {
3472 let row_id = {
3473 let chat_name = &chat_name;
3474 let grpid = &grpid;
3475 let trans_fn = |t: &mut rusqlite::Transaction| {
3476 let cnt = t.execute("UPDATE chats SET name=? WHERE grpid=?", (chat_name, grpid))?;
3477 ensure!(cnt <= 1, "{cnt} chats exist with grpid {grpid}");
3478 if cnt == 1 {
3479 return Ok(t.query_row(
3480 "SELECT id FROM chats WHERE grpid=? AND type=?",
3481 (grpid, Chattype::OutBroadcast),
3482 |row| {
3483 let id: isize = row.get(0)?;
3484 Ok(id)
3485 },
3486 )?);
3487 }
3488 t.execute(
3489 "INSERT INTO chats \
3490 (type, name, grpid, param, created_timestamp) \
3491 VALUES(?, ?, ?, \'U=1\', ?);",
3492 (
3493 Chattype::OutBroadcast,
3494 &chat_name,
3495 &grpid,
3496 create_smeared_timestamp(context),
3497 ),
3498 )?;
3499 Ok(t.last_insert_rowid().try_into()?)
3500 };
3501 context.sql.transaction(trans_fn).await?
3502 };
3503 let chat_id = ChatId::new(u32::try_from(row_id)?);
3504
3505 context.emit_msgs_changed_without_ids();
3506 chatlist_events::emit_chatlist_changed(context);
3507
3508 if sync.into() {
3509 let id = SyncId::Grpid(grpid);
3510 let action = SyncAction::CreateBroadcast(chat_name);
3511 self::sync(context, id, action).await.log_err(context).ok();
3512 }
3513
3514 Ok(chat_id)
3515}
3516
3517pub(crate) async fn update_chat_contacts_table(
3519 context: &Context,
3520 timestamp: i64,
3521 id: ChatId,
3522 contacts: &HashSet<ContactId>,
3523) -> Result<()> {
3524 context
3525 .sql
3526 .transaction(move |transaction| {
3527 transaction.execute(
3531 "UPDATE chats_contacts
3532 SET remove_timestamp=MAX(add_timestamp+1, ?)
3533 WHERE chat_id=?",
3534 (timestamp, id),
3535 )?;
3536
3537 if !contacts.is_empty() {
3538 let mut statement = transaction.prepare(
3539 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
3540 VALUES (?1, ?2, ?3)
3541 ON CONFLICT (chat_id, contact_id)
3542 DO UPDATE SET add_timestamp=remove_timestamp",
3543 )?;
3544
3545 for contact_id in contacts {
3546 statement.execute((id, contact_id, timestamp))?;
3550 }
3551 }
3552 Ok(())
3553 })
3554 .await?;
3555 Ok(())
3556}
3557
3558pub(crate) async fn add_to_chat_contacts_table(
3560 context: &Context,
3561 timestamp: i64,
3562 chat_id: ChatId,
3563 contact_ids: &[ContactId],
3564) -> Result<()> {
3565 context
3566 .sql
3567 .transaction(move |transaction| {
3568 let mut add_statement = transaction.prepare(
3569 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp) VALUES(?1, ?2, ?3)
3570 ON CONFLICT (chat_id, contact_id)
3571 DO UPDATE SET add_timestamp=MAX(remove_timestamp, ?3)",
3572 )?;
3573
3574 for contact_id in contact_ids {
3575 add_statement.execute((chat_id, contact_id, timestamp))?;
3576 }
3577 Ok(())
3578 })
3579 .await?;
3580
3581 Ok(())
3582}
3583
3584pub(crate) async fn remove_from_chat_contacts_table(
3587 context: &Context,
3588 chat_id: ChatId,
3589 contact_id: ContactId,
3590) -> Result<()> {
3591 let now = time();
3592 context
3593 .sql
3594 .execute(
3595 "UPDATE chats_contacts
3596 SET remove_timestamp=MAX(add_timestamp+1, ?)
3597 WHERE chat_id=? AND contact_id=?",
3598 (now, chat_id, contact_id),
3599 )
3600 .await?;
3601 Ok(())
3602}
3603
3604pub async fn add_contact_to_chat(
3607 context: &Context,
3608 chat_id: ChatId,
3609 contact_id: ContactId,
3610) -> Result<()> {
3611 add_contact_to_chat_ex(context, Sync, chat_id, contact_id, false).await?;
3612 Ok(())
3613}
3614
3615pub(crate) async fn add_contact_to_chat_ex(
3616 context: &Context,
3617 mut sync: sync::Sync,
3618 chat_id: ChatId,
3619 contact_id: ContactId,
3620 from_handshake: bool,
3621) -> Result<bool> {
3622 ensure!(!chat_id.is_special(), "can not add member to special chats");
3623 let contact = Contact::get_by_id(context, contact_id).await?;
3624 let mut msg = Message::new(Viewtype::default());
3625
3626 chat_id.reset_gossiped_timestamp(context).await?;
3627
3628 let mut chat = Chat::load_from_db(context, chat_id).await?;
3630 ensure!(
3631 chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast,
3632 "{chat_id} is not a group/broadcast where one can add members"
3633 );
3634 ensure!(
3635 Contact::real_exists_by_id(context, contact_id).await? || contact_id == ContactId::SELF,
3636 "invalid contact_id {contact_id} for adding to group"
3637 );
3638 ensure!(!chat.is_mailing_list(), "Mailing lists can't be changed");
3639 ensure!(
3640 chat.typ != Chattype::OutBroadcast || contact_id != ContactId::SELF,
3641 "Cannot add SELF to broadcast channel."
3642 );
3643 ensure!(
3644 chat.is_encrypted(context).await? == contact.is_key_contact(),
3645 "Only key-contacts can be added to encrypted chats"
3646 );
3647
3648 if !chat.is_self_in_chat(context).await? {
3649 context.emit_event(EventType::ErrorSelfNotInGroup(
3650 "Cannot add contact to group; self not in group.".into(),
3651 ));
3652 bail!("can not add contact because the account is not part of the group/broadcast");
3653 }
3654
3655 let sync_qr_code_tokens;
3656 if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
3657 chat.param
3658 .remove(Param::Unpromoted)
3659 .set_i64(Param::GroupNameTimestamp, smeared_time(context));
3660 chat.update_param(context).await?;
3661 sync_qr_code_tokens = true;
3662 } else {
3663 sync_qr_code_tokens = false;
3664 }
3665
3666 if context.is_self_addr(contact.get_addr()).await? {
3667 warn!(
3670 context,
3671 "Invalid attempt to add self e-mail address to group."
3672 );
3673 return Ok(false);
3674 }
3675
3676 if is_contact_in_chat(context, chat_id, contact_id).await? {
3677 if !from_handshake {
3678 return Ok(true);
3679 }
3680 } else {
3681 if is_contact_in_chat(context, chat_id, contact_id).await? {
3683 return Ok(false);
3684 }
3685 add_to_chat_contacts_table(context, time(), chat_id, &[contact_id]).await?;
3686 }
3687 if chat.typ == Chattype::Group && chat.is_promoted() {
3688 msg.viewtype = Viewtype::Text;
3689
3690 let contact_addr = contact.get_addr().to_lowercase();
3691 msg.text = stock_str::msg_add_member_local(context, contact.id, ContactId::SELF).await;
3692 msg.param.set_cmd(SystemMessage::MemberAddedToGroup);
3693 msg.param.set(Param::Arg, contact_addr);
3694 msg.param.set_int(Param::Arg2, from_handshake.into());
3695 msg.param
3696 .set_int(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
3697 send_msg(context, chat_id, &mut msg).await?;
3698
3699 sync = Nosync;
3700 if sync_qr_code_tokens
3706 && context
3707 .sync_qr_code_tokens(Some(chat.grpid.as_str()))
3708 .await
3709 .log_err(context)
3710 .is_ok()
3711 {
3712 context.scheduler.interrupt_inbox().await;
3713 }
3714 }
3715 context.emit_event(EventType::ChatModified(chat_id));
3716 if sync.into() {
3717 chat.sync_contacts(context).await.log_err(context).ok();
3718 }
3719 Ok(true)
3720}
3721
3722pub(crate) async fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result<bool> {
3728 let timestamp_some_days_ago = time() - DC_RESEND_USER_AVATAR_DAYS * 24 * 60 * 60;
3729 let needs_attach = context
3730 .sql
3731 .query_map(
3732 "SELECT c.selfavatar_sent
3733 FROM chats_contacts cc
3734 LEFT JOIN contacts c ON c.id=cc.contact_id
3735 WHERE cc.chat_id=? AND cc.contact_id!=? AND cc.add_timestamp >= cc.remove_timestamp",
3736 (chat_id, ContactId::SELF),
3737 |row| Ok(row.get::<_, i64>(0)),
3738 |rows| {
3739 let mut needs_attach = false;
3740 for row in rows {
3741 let row = row?;
3742 let selfavatar_sent = row?;
3743 if selfavatar_sent < timestamp_some_days_ago {
3744 needs_attach = true;
3745 }
3746 }
3747 Ok(needs_attach)
3748 },
3749 )
3750 .await?;
3751 Ok(needs_attach)
3752}
3753
3754#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
3756pub enum MuteDuration {
3757 NotMuted,
3759
3760 Forever,
3762
3763 Until(std::time::SystemTime),
3765}
3766
3767impl rusqlite::types::ToSql for MuteDuration {
3768 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
3769 let duration: i64 = match &self {
3770 MuteDuration::NotMuted => 0,
3771 MuteDuration::Forever => -1,
3772 MuteDuration::Until(when) => {
3773 let duration = when
3774 .duration_since(SystemTime::UNIX_EPOCH)
3775 .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
3776 i64::try_from(duration.as_secs())
3777 .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?
3778 }
3779 };
3780 let val = rusqlite::types::Value::Integer(duration);
3781 let out = rusqlite::types::ToSqlOutput::Owned(val);
3782 Ok(out)
3783 }
3784}
3785
3786impl rusqlite::types::FromSql for MuteDuration {
3787 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
3788 match i64::column_result(value)? {
3791 0 => Ok(MuteDuration::NotMuted),
3792 -1 => Ok(MuteDuration::Forever),
3793 n if n > 0 => match SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(n as u64)) {
3794 Some(t) => Ok(MuteDuration::Until(t)),
3795 None => Err(rusqlite::types::FromSqlError::OutOfRange(n)),
3796 },
3797 _ => Ok(MuteDuration::NotMuted),
3798 }
3799 }
3800}
3801
3802pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<()> {
3804 set_muted_ex(context, Sync, chat_id, duration).await
3805}
3806
3807pub(crate) async fn set_muted_ex(
3808 context: &Context,
3809 sync: sync::Sync,
3810 chat_id: ChatId,
3811 duration: MuteDuration,
3812) -> Result<()> {
3813 ensure!(!chat_id.is_special(), "Invalid chat ID");
3814 context
3815 .sql
3816 .execute(
3817 "UPDATE chats SET muted_until=? WHERE id=?;",
3818 (duration, chat_id),
3819 )
3820 .await
3821 .context(format!("Failed to set mute duration for {chat_id}"))?;
3822 context.emit_event(EventType::ChatModified(chat_id));
3823 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3824 if sync.into() {
3825 let chat = Chat::load_from_db(context, chat_id).await?;
3826 chat.sync(context, SyncAction::SetMuted(duration))
3827 .await
3828 .log_err(context)
3829 .ok();
3830 }
3831 Ok(())
3832}
3833
3834pub async fn remove_contact_from_chat(
3836 context: &Context,
3837 chat_id: ChatId,
3838 contact_id: ContactId,
3839) -> Result<()> {
3840 ensure!(
3841 !chat_id.is_special(),
3842 "bad chat_id, can not be special chat: {chat_id}"
3843 );
3844 ensure!(
3845 !contact_id.is_special() || contact_id == ContactId::SELF,
3846 "Cannot remove special contact"
3847 );
3848
3849 let chat = Chat::load_from_db(context, chat_id).await?;
3850 if chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast {
3851 if !chat.is_self_in_chat(context).await? {
3852 let err_msg = format!(
3853 "Cannot remove contact {contact_id} from chat {chat_id}: self not in group."
3854 );
3855 context.emit_event(EventType::ErrorSelfNotInGroup(err_msg.clone()));
3856 bail!("{err_msg}");
3857 } else {
3858 let mut sync = Nosync;
3859
3860 if chat.is_promoted() {
3861 remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
3862 } else {
3863 context
3864 .sql
3865 .execute(
3866 "DELETE FROM chats_contacts
3867 WHERE chat_id=? AND contact_id=?",
3868 (chat_id, contact_id),
3869 )
3870 .await?;
3871 }
3872
3873 if let Some(contact) = Contact::get_by_id_optional(context, contact_id).await? {
3877 if chat.typ == Chattype::Group && chat.is_promoted() {
3878 let addr = contact.get_addr();
3879
3880 let res = send_member_removal_msg(context, &chat, contact_id, addr).await;
3881
3882 if contact_id == ContactId::SELF {
3883 res?;
3884 } else if let Err(e) = res {
3885 warn!(
3886 context,
3887 "remove_contact_from_chat({chat_id}, {contact_id}): send_msg() failed: {e:#}."
3888 );
3889 }
3890 } else {
3891 sync = Sync;
3892 }
3893 }
3894 context.emit_event(EventType::ChatModified(chat_id));
3895 if sync.into() {
3896 chat.sync_contacts(context).await.log_err(context).ok();
3897 }
3898 }
3899 } else if chat.typ == Chattype::InBroadcast && contact_id == ContactId::SELF {
3900 let self_addr = context.get_primary_self_addr().await?;
3903 send_member_removal_msg(context, &chat, contact_id, &self_addr).await?;
3904 } else {
3905 bail!("Cannot remove members from non-group chats.");
3906 }
3907
3908 Ok(())
3909}
3910
3911async fn send_member_removal_msg(
3912 context: &Context,
3913 chat: &Chat,
3914 contact_id: ContactId,
3915 addr: &str,
3916) -> Result<MsgId> {
3917 let mut msg = Message::new(Viewtype::Text);
3918
3919 if contact_id == ContactId::SELF {
3920 if chat.typ == Chattype::InBroadcast {
3921 msg.text = stock_str::msg_you_left_broadcast(context).await;
3922 } else {
3923 msg.text = stock_str::msg_group_left_local(context, ContactId::SELF).await;
3924 }
3925 } else {
3926 msg.text = stock_str::msg_del_member_local(context, contact_id, ContactId::SELF).await;
3927 }
3928
3929 msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup);
3930 msg.param.set(Param::Arg, addr.to_lowercase());
3931 msg.param
3932 .set(Param::ContactAddedRemoved, contact_id.to_u32());
3933
3934 send_msg(context, chat.id, &mut msg).await
3935}
3936
3937pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -> Result<()> {
3939 rename_ex(context, Sync, chat_id, new_name).await
3940}
3941
3942async fn rename_ex(
3943 context: &Context,
3944 mut sync: sync::Sync,
3945 chat_id: ChatId,
3946 new_name: &str,
3947) -> Result<()> {
3948 let new_name = sanitize_single_line(new_name);
3949 let mut success = false;
3951
3952 ensure!(!new_name.is_empty(), "Invalid name");
3953 ensure!(!chat_id.is_special(), "Invalid chat ID");
3954
3955 let chat = Chat::load_from_db(context, chat_id).await?;
3956 let mut msg = Message::new(Viewtype::default());
3957
3958 if chat.typ == Chattype::Group
3959 || chat.typ == Chattype::Mailinglist
3960 || chat.typ == Chattype::OutBroadcast
3961 {
3962 if chat.name == new_name {
3963 success = true;
3964 } else if !chat.is_self_in_chat(context).await? {
3965 context.emit_event(EventType::ErrorSelfNotInGroup(
3966 "Cannot set chat name; self not in group".into(),
3967 ));
3968 } else {
3969 context
3970 .sql
3971 .execute(
3972 "UPDATE chats SET name=? WHERE id=?;",
3973 (new_name.to_string(), chat_id),
3974 )
3975 .await?;
3976 if chat.is_promoted()
3977 && !chat.is_mailing_list()
3978 && sanitize_single_line(&chat.name) != new_name
3979 {
3980 msg.viewtype = Viewtype::Text;
3981 msg.text =
3982 stock_str::msg_grp_name(context, &chat.name, &new_name, ContactId::SELF).await;
3983 msg.param.set_cmd(SystemMessage::GroupNameChanged);
3984 if !chat.name.is_empty() {
3985 msg.param.set(Param::Arg, &chat.name);
3986 }
3987 msg.id = send_msg(context, chat_id, &mut msg).await?;
3988 context.emit_msgs_changed(chat_id, msg.id);
3989 sync = Nosync;
3990 }
3991 context.emit_event(EventType::ChatModified(chat_id));
3992 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3993 success = true;
3994 }
3995 }
3996
3997 if !success {
3998 bail!("Failed to set name");
3999 }
4000 if sync.into() && chat.name != new_name {
4001 let sync_name = new_name.to_string();
4002 chat.sync(context, SyncAction::Rename(sync_name))
4003 .await
4004 .log_err(context)
4005 .ok();
4006 }
4007 Ok(())
4008}
4009
4010pub async fn set_chat_profile_image(
4016 context: &Context,
4017 chat_id: ChatId,
4018 new_image: &str, ) -> Result<()> {
4020 ensure!(!chat_id.is_special(), "Invalid chat ID");
4021 let mut chat = Chat::load_from_db(context, chat_id).await?;
4022 ensure!(
4023 chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast,
4024 "Can only set profile image for groups / broadcasts"
4025 );
4026 ensure!(
4027 !chat.grpid.is_empty(),
4028 "Cannot set profile image for ad hoc groups"
4029 );
4030 if !chat.is_self_in_chat(context).await? {
4032 context.emit_event(EventType::ErrorSelfNotInGroup(
4033 "Cannot set chat profile image; self not in group.".into(),
4034 ));
4035 bail!("Failed to set profile image");
4036 }
4037 let mut msg = Message::new(Viewtype::Text);
4038 msg.param
4039 .set_int(Param::Cmd, SystemMessage::GroupImageChanged as i32);
4040 if new_image.is_empty() {
4041 chat.param.remove(Param::ProfileImage);
4042 msg.param.remove(Param::Arg);
4043 msg.text = stock_str::msg_grp_img_deleted(context, ContactId::SELF).await;
4044 } else {
4045 let mut image_blob = BlobObject::create_and_deduplicate(
4046 context,
4047 Path::new(new_image),
4048 Path::new(new_image),
4049 )?;
4050 image_blob.recode_to_avatar_size(context).await?;
4051 chat.param.set(Param::ProfileImage, image_blob.as_name());
4052 msg.param.set(Param::Arg, image_blob.as_name());
4053 msg.text = stock_str::msg_grp_img_changed(context, ContactId::SELF).await;
4054 }
4055 chat.update_param(context).await?;
4056 if chat.is_promoted() {
4057 msg.id = send_msg(context, chat_id, &mut msg).await?;
4058 context.emit_msgs_changed(chat_id, msg.id);
4059 }
4060 context.emit_event(EventType::ChatModified(chat_id));
4061 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4062 Ok(())
4063}
4064
4065pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Result<()> {
4067 ensure!(!msg_ids.is_empty(), "empty msgs_ids: nothing to forward");
4068 ensure!(!chat_id.is_special(), "can not forward to special chat");
4069
4070 let mut created_msgs: Vec<MsgId> = Vec::new();
4071 let mut curr_timestamp: i64;
4072
4073 chat_id
4074 .unarchive_if_not_muted(context, MessageState::Undefined)
4075 .await?;
4076 let mut chat = Chat::load_from_db(context, chat_id).await?;
4077 if let Some(reason) = chat.why_cant_send(context).await? {
4078 bail!("cannot send to {chat_id}: {reason}");
4079 }
4080 curr_timestamp = create_smeared_timestamps(context, msg_ids.len());
4081 let mut msgs = Vec::with_capacity(msg_ids.len());
4082 for id in msg_ids {
4083 let ts: i64 = context
4084 .sql
4085 .query_get_value("SELECT timestamp FROM msgs WHERE id=?", (id,))
4086 .await?
4087 .with_context(|| format!("No message {id}"))?;
4088 msgs.push((ts, *id));
4089 }
4090 msgs.sort_unstable();
4091 for (_, id) in msgs {
4092 let src_msg_id: MsgId = id;
4093 let mut msg = Message::load_from_db(context, src_msg_id).await?;
4094 if msg.state == MessageState::OutDraft {
4095 bail!("cannot forward drafts.");
4096 }
4097
4098 if msg.get_viewtype() != Viewtype::Sticker {
4099 msg.param
4100 .set_int(Param::Forwarded, src_msg_id.to_u32() as i32);
4101 }
4102
4103 if msg.get_viewtype() == Viewtype::Call {
4104 msg.viewtype = Viewtype::Text;
4105 }
4106
4107 msg.param.remove(Param::GuaranteeE2ee);
4108 msg.param.remove(Param::ForcePlaintext);
4109 msg.param.remove(Param::Cmd);
4110 msg.param.remove(Param::OverrideSenderDisplayname);
4111 msg.param.remove(Param::WebxdcDocument);
4112 msg.param.remove(Param::WebxdcDocumentTimestamp);
4113 msg.param.remove(Param::WebxdcSummary);
4114 msg.param.remove(Param::WebxdcSummaryTimestamp);
4115 msg.param.remove(Param::IsEdited);
4116 msg.param.remove(Param::WebrtcRoom);
4117 msg.param.remove(Param::WebrtcAccepted);
4118 msg.in_reply_to = None;
4119
4120 msg.subject = "".to_string();
4122
4123 msg.state = MessageState::OutPending;
4124 msg.rfc724_mid = create_outgoing_rfc724_mid();
4125 msg.timestamp_sort = curr_timestamp;
4126 chat.prepare_msg_raw(context, &mut msg, None).await?;
4127
4128 curr_timestamp += 1;
4129 if !create_send_msg_jobs(context, &mut msg).await?.is_empty() {
4130 context.scheduler.interrupt_smtp().await;
4131 }
4132 created_msgs.push(msg.id);
4133 }
4134 for msg_id in created_msgs {
4135 context.emit_msgs_changed(chat_id, msg_id);
4136 }
4137 Ok(())
4138}
4139
4140pub async fn save_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
4143 let mut msgs = Vec::with_capacity(msg_ids.len());
4144 for id in msg_ids {
4145 let ts: i64 = context
4146 .sql
4147 .query_get_value("SELECT timestamp FROM msgs WHERE id=?", (id,))
4148 .await?
4149 .with_context(|| format!("No message {id}"))?;
4150 msgs.push((ts, *id));
4151 }
4152 msgs.sort_unstable();
4153 for (_, src_msg_id) in msgs {
4154 let dest_rfc724_mid = create_outgoing_rfc724_mid();
4155 let src_rfc724_mid = save_copy_in_self_talk(context, src_msg_id, &dest_rfc724_mid).await?;
4156 context
4157 .add_sync_item(SyncData::SaveMessage {
4158 src: src_rfc724_mid,
4159 dest: dest_rfc724_mid,
4160 })
4161 .await?;
4162 }
4163 context.scheduler.interrupt_inbox().await;
4164 Ok(())
4165}
4166
4167pub(crate) async fn save_copy_in_self_talk(
4173 context: &Context,
4174 src_msg_id: MsgId,
4175 dest_rfc724_mid: &String,
4176) -> Result<String> {
4177 let dest_chat_id = ChatId::create_for_contact(context, ContactId::SELF).await?;
4178 let mut msg = Message::load_from_db(context, src_msg_id).await?;
4179 msg.param.remove(Param::Cmd);
4180 msg.param.remove(Param::WebxdcDocument);
4181 msg.param.remove(Param::WebxdcDocumentTimestamp);
4182 msg.param.remove(Param::WebxdcSummary);
4183 msg.param.remove(Param::WebxdcSummaryTimestamp);
4184
4185 if !msg.original_msg_id.is_unset() {
4186 bail!("message already saved.");
4187 }
4188
4189 let copy_fields = "from_id, to_id, timestamp_rcvd, type, txt,
4190 mime_modified, mime_headers, mime_compressed, mime_in_reply_to, subject, msgrmsg";
4191 let row_id = context
4192 .sql
4193 .insert(
4194 &format!(
4195 "INSERT INTO msgs ({copy_fields},
4196 timestamp_sent,
4197 chat_id, rfc724_mid, state, timestamp, param, starred)
4198 SELECT {copy_fields},
4199 -- Outgoing messages on originating device
4200 -- have timestamp_sent == 0.
4201 -- We copy sort timestamp instead
4202 -- so UIs display the same timestamp
4203 -- for saved and original message.
4204 IIF(timestamp_sent == 0, timestamp, timestamp_sent),
4205 ?, ?, ?, ?, ?, ?
4206 FROM msgs WHERE id=?;"
4207 ),
4208 (
4209 dest_chat_id,
4210 dest_rfc724_mid,
4211 if msg.from_id == ContactId::SELF {
4212 MessageState::OutDelivered
4213 } else {
4214 MessageState::InSeen
4215 },
4216 create_smeared_timestamp(context),
4217 msg.param.to_string(),
4218 src_msg_id,
4219 src_msg_id,
4220 ),
4221 )
4222 .await?;
4223 let dest_msg_id = MsgId::new(row_id.try_into()?);
4224
4225 context.emit_msgs_changed(msg.chat_id, src_msg_id);
4226 context.emit_msgs_changed(dest_chat_id, dest_msg_id);
4227 chatlist_events::emit_chatlist_changed(context);
4228 chatlist_events::emit_chatlist_item_changed(context, dest_chat_id);
4229
4230 Ok(msg.rfc724_mid)
4231}
4232
4233pub async fn resend_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
4237 let mut msgs: Vec<Message> = Vec::new();
4238 for msg_id in msg_ids {
4239 let msg = Message::load_from_db(context, *msg_id).await?;
4240 ensure!(
4241 msg.from_id == ContactId::SELF,
4242 "can resend only own messages"
4243 );
4244 ensure!(!msg.is_info(), "cannot resend info messages");
4245 msgs.push(msg)
4246 }
4247
4248 for mut msg in msgs {
4249 match msg.get_state() {
4250 MessageState::OutPending
4252 | MessageState::OutFailed
4253 | MessageState::OutDelivered
4254 | MessageState::OutMdnRcvd => {
4255 message::update_msg_state(context, msg.id, MessageState::OutPending).await?
4256 }
4257 msg_state => bail!("Unexpected message state {msg_state}"),
4258 }
4259 msg.timestamp_sort = create_smeared_timestamp(context);
4260 if create_send_msg_jobs(context, &mut msg).await?.is_empty() {
4261 continue;
4262 }
4263
4264 context.emit_event(EventType::MsgsChanged {
4268 chat_id: msg.chat_id,
4269 msg_id: msg.id,
4270 });
4271 chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
4273
4274 if msg.viewtype == Viewtype::Webxdc {
4275 let conn_fn = |conn: &mut rusqlite::Connection| {
4276 let range = conn.query_row(
4277 "SELECT IFNULL(min(id), 1), IFNULL(max(id), 0) \
4278 FROM msgs_status_updates WHERE msg_id=?",
4279 (msg.id,),
4280 |row| {
4281 let min_id: StatusUpdateSerial = row.get(0)?;
4282 let max_id: StatusUpdateSerial = row.get(1)?;
4283 Ok((min_id, max_id))
4284 },
4285 )?;
4286 if range.0 > range.1 {
4287 return Ok(());
4288 };
4289 conn.execute(
4293 "INSERT INTO smtp_status_updates (msg_id, first_serial, last_serial, descr) \
4294 VALUES(?, ?, ?, '') \
4295 ON CONFLICT(msg_id) \
4296 DO UPDATE SET first_serial=min(first_serial - 1, excluded.first_serial)",
4297 (msg.id, range.0, range.1),
4298 )?;
4299 Ok(())
4300 };
4301 context.sql.call_write(conn_fn).await?;
4302 }
4303 context.scheduler.interrupt_smtp().await;
4304 }
4305 Ok(())
4306}
4307
4308pub(crate) async fn get_chat_cnt(context: &Context) -> Result<usize> {
4309 if context.sql.is_open().await {
4310 let count = context
4312 .sql
4313 .count("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;", ())
4314 .await?;
4315 Ok(count)
4316 } else {
4317 Ok(0)
4318 }
4319}
4320
4321pub(crate) async fn get_chat_id_by_grpid(
4323 context: &Context,
4324 grpid: &str,
4325) -> Result<Option<(ChatId, Blocked)>> {
4326 context
4327 .sql
4328 .query_row_optional(
4329 "SELECT id, blocked FROM chats WHERE grpid=?;",
4330 (grpid,),
4331 |row| {
4332 let chat_id = row.get::<_, ChatId>(0)?;
4333
4334 let b = row.get::<_, Option<Blocked>>(1)?.unwrap_or_default();
4335 Ok((chat_id, b))
4336 },
4337 )
4338 .await
4339}
4340
4341pub async fn add_device_msg_with_importance(
4346 context: &Context,
4347 label: Option<&str>,
4348 msg: Option<&mut Message>,
4349 important: bool,
4350) -> Result<MsgId> {
4351 ensure!(
4352 label.is_some() || msg.is_some(),
4353 "device-messages need label, msg or both"
4354 );
4355 let mut chat_id = ChatId::new(0);
4356 let mut msg_id = MsgId::new_unset();
4357
4358 if let Some(label) = label {
4359 if was_device_msg_ever_added(context, label).await? {
4360 info!(context, "Device-message {label} already added.");
4361 return Ok(msg_id);
4362 }
4363 }
4364
4365 if let Some(msg) = msg {
4366 chat_id = ChatId::get_for_contact(context, ContactId::DEVICE).await?;
4367
4368 let rfc724_mid = create_outgoing_rfc724_mid();
4369 let timestamp_sent = create_smeared_timestamp(context);
4370
4371 msg.timestamp_sort = timestamp_sent;
4374 if let Some(last_msg_time) = chat_id.get_timestamp(context).await? {
4375 if msg.timestamp_sort <= last_msg_time {
4376 msg.timestamp_sort = last_msg_time + 1;
4377 }
4378 }
4379 prepare_msg_blob(context, msg).await?;
4380 let state = MessageState::InFresh;
4381 let row_id = context
4382 .sql
4383 .insert(
4384 "INSERT INTO msgs (
4385 chat_id,
4386 from_id,
4387 to_id,
4388 timestamp,
4389 timestamp_sent,
4390 timestamp_rcvd,
4391 type,state,
4392 txt,
4393 txt_normalized,
4394 param,
4395 rfc724_mid)
4396 VALUES (?,?,?,?,?,?,?,?,?,?,?,?);",
4397 (
4398 chat_id,
4399 ContactId::DEVICE,
4400 ContactId::SELF,
4401 msg.timestamp_sort,
4402 timestamp_sent,
4403 timestamp_sent, msg.viewtype,
4405 state,
4406 &msg.text,
4407 message::normalize_text(&msg.text),
4408 msg.param.to_string(),
4409 rfc724_mid,
4410 ),
4411 )
4412 .await?;
4413 context.new_msgs_notify.notify_one();
4414
4415 msg_id = MsgId::new(u32::try_from(row_id)?);
4416 if !msg.hidden {
4417 chat_id.unarchive_if_not_muted(context, state).await?;
4418 }
4419 }
4420
4421 if let Some(label) = label {
4422 context
4423 .sql
4424 .execute("INSERT INTO devmsglabels (label) VALUES (?);", (label,))
4425 .await?;
4426 }
4427
4428 if !msg_id.is_unset() {
4429 chat_id.emit_msg_event(context, msg_id, important);
4430 }
4431
4432 Ok(msg_id)
4433}
4434
4435pub async fn add_device_msg(
4437 context: &Context,
4438 label: Option<&str>,
4439 msg: Option<&mut Message>,
4440) -> Result<MsgId> {
4441 add_device_msg_with_importance(context, label, msg, false).await
4442}
4443
4444pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result<bool> {
4446 ensure!(!label.is_empty(), "empty label");
4447 let exists = context
4448 .sql
4449 .exists(
4450 "SELECT COUNT(label) FROM devmsglabels WHERE label=?",
4451 (label,),
4452 )
4453 .await?;
4454
4455 Ok(exists)
4456}
4457
4458pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Result<()> {
4466 context
4467 .sql
4468 .execute("DELETE FROM msgs WHERE from_id=?;", (ContactId::DEVICE,))
4469 .await?;
4470 context.sql.execute("DELETE FROM devmsglabels;", ()).await?;
4471
4472 context
4474 .sql
4475 .execute(
4476 r#"INSERT INTO devmsglabels (label) VALUES ("core-welcome-image"), ("core-welcome")"#,
4477 (),
4478 )
4479 .await?;
4480 context
4481 .set_config_internal(Config::QuotaExceeding, None)
4482 .await?;
4483 Ok(())
4484}
4485
4486#[expect(clippy::too_many_arguments)]
4491pub(crate) async fn add_info_msg_with_cmd(
4492 context: &Context,
4493 chat_id: ChatId,
4494 text: &str,
4495 cmd: SystemMessage,
4496 timestamp_sort: i64,
4497 timestamp_sent_rcvd: Option<i64>,
4499 parent: Option<&Message>,
4500 from_id: Option<ContactId>,
4501 added_removed_id: Option<ContactId>,
4502) -> Result<MsgId> {
4503 let rfc724_mid = create_outgoing_rfc724_mid();
4504 let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?;
4505
4506 let mut param = Params::new();
4507 if cmd != SystemMessage::Unknown {
4508 param.set_cmd(cmd);
4509 }
4510 if let Some(contact_id) = added_removed_id {
4511 param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
4512 }
4513
4514 let row_id =
4515 context.sql.insert(
4516 "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)
4517 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?);",
4518 (
4519 chat_id,
4520 from_id.unwrap_or(ContactId::INFO),
4521 ContactId::INFO,
4522 timestamp_sort,
4523 timestamp_sent_rcvd.unwrap_or(0),
4524 timestamp_sent_rcvd.unwrap_or(0),
4525 Viewtype::Text,
4526 MessageState::InNoticed,
4527 text,
4528 message::normalize_text(text),
4529 rfc724_mid,
4530 ephemeral_timer,
4531 param.to_string(),
4532 parent.map(|msg|msg.rfc724_mid.clone()).unwrap_or_default()
4533 )
4534 ).await?;
4535 context.new_msgs_notify.notify_one();
4536
4537 let msg_id = MsgId::new(row_id.try_into()?);
4538 context.emit_msgs_changed(chat_id, msg_id);
4539
4540 Ok(msg_id)
4541}
4542
4543pub(crate) async fn add_info_msg(
4545 context: &Context,
4546 chat_id: ChatId,
4547 text: &str,
4548 timestamp: i64,
4549) -> Result<MsgId> {
4550 add_info_msg_with_cmd(
4551 context,
4552 chat_id,
4553 text,
4554 SystemMessage::Unknown,
4555 timestamp,
4556 None,
4557 None,
4558 None,
4559 None,
4560 )
4561 .await
4562}
4563
4564pub(crate) async fn update_msg_text_and_timestamp(
4565 context: &Context,
4566 chat_id: ChatId,
4567 msg_id: MsgId,
4568 text: &str,
4569 timestamp: i64,
4570) -> Result<()> {
4571 context
4572 .sql
4573 .execute(
4574 "UPDATE msgs SET txt=?, txt_normalized=?, timestamp=? WHERE id=?;",
4575 (text, message::normalize_text(text), timestamp, msg_id),
4576 )
4577 .await?;
4578 context.emit_msgs_changed(chat_id, msg_id);
4579 Ok(())
4580}
4581
4582async fn set_contacts_by_addrs(context: &Context, id: ChatId, addrs: &[String]) -> Result<()> {
4584 let chat = Chat::load_from_db(context, id).await?;
4585 ensure!(
4586 !chat.is_encrypted(context).await?,
4587 "Cannot add address-contacts to encrypted chat {id}"
4588 );
4589 ensure!(
4590 chat.typ == Chattype::OutBroadcast,
4591 "{id} is not a broadcast list",
4592 );
4593 let mut contacts = HashSet::new();
4594 for addr in addrs {
4595 let contact_addr = ContactAddress::new(addr)?;
4596 let contact = Contact::add_or_lookup(context, "", &contact_addr, Origin::Hidden)
4597 .await?
4598 .0;
4599 contacts.insert(contact);
4600 }
4601 let contacts_old = HashSet::<ContactId>::from_iter(get_chat_contacts(context, id).await?);
4602 if contacts == contacts_old {
4603 return Ok(());
4604 }
4605 context
4606 .sql
4607 .transaction(move |transaction| {
4608 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (id,))?;
4609
4610 let mut statement = transaction
4613 .prepare("INSERT INTO chats_contacts (chat_id, contact_id) VALUES (?, ?)")?;
4614 for contact_id in &contacts {
4615 statement.execute((id, contact_id))?;
4616 }
4617 Ok(())
4618 })
4619 .await?;
4620 context.emit_event(EventType::ChatModified(id));
4621 Ok(())
4622}
4623
4624async fn set_contacts_by_fingerprints(
4628 context: &Context,
4629 id: ChatId,
4630 fingerprint_addrs: &[(String, String)],
4631) -> Result<()> {
4632 let chat = Chat::load_from_db(context, id).await?;
4633 ensure!(
4634 chat.is_encrypted(context).await?,
4635 "Cannot add key-contacts to unencrypted chat {id}"
4636 );
4637 ensure!(
4638 matches!(chat.typ, Chattype::Group | Chattype::OutBroadcast),
4639 "{id} is not a group or broadcast",
4640 );
4641 let mut contacts = HashSet::new();
4642 for (fingerprint, addr) in fingerprint_addrs {
4643 let contact = Contact::add_or_lookup_ex(context, "", addr, fingerprint, Origin::Hidden)
4644 .await?
4645 .0;
4646 contacts.insert(contact);
4647 }
4648 let contacts_old = HashSet::<ContactId>::from_iter(get_chat_contacts(context, id).await?);
4649 if contacts == contacts_old {
4650 return Ok(());
4651 }
4652 context
4653 .sql
4654 .transaction(move |transaction| {
4655 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (id,))?;
4656
4657 let mut statement = transaction
4660 .prepare("INSERT INTO chats_contacts (chat_id, contact_id) VALUES (?, ?)")?;
4661 for contact_id in &contacts {
4662 statement.execute((id, contact_id))?;
4663 }
4664 Ok(())
4665 })
4666 .await?;
4667 context.emit_event(EventType::ChatModified(id));
4668 Ok(())
4669}
4670
4671#[derive(Debug, Serialize, Deserialize, PartialEq)]
4673pub(crate) enum SyncId {
4674 ContactAddr(String),
4676
4677 ContactFingerprint(String),
4679
4680 Grpid(String),
4681 Msgids(Vec<String>),
4683
4684 Device,
4686}
4687
4688#[derive(Debug, Serialize, Deserialize, PartialEq)]
4690pub(crate) enum SyncAction {
4691 Block,
4692 Unblock,
4693 Accept,
4694 SetVisibility(ChatVisibility),
4695 SetMuted(MuteDuration),
4696 CreateBroadcast(String),
4698 CreateGroupEncrypted(String),
4700 Rename(String),
4701 SetContacts(Vec<String>),
4703 SetPgpContacts(Vec<(String, String)>),
4707 Delete,
4708}
4709
4710impl Context {
4711 pub(crate) async fn sync_alter_chat(&self, id: &SyncId, action: &SyncAction) -> Result<()> {
4713 let chat_id = match id {
4714 SyncId::ContactAddr(addr) => {
4715 if let SyncAction::Rename(to) = action {
4716 Contact::create_ex(self, Nosync, to, addr).await?;
4717 return Ok(());
4718 }
4719 let addr = ContactAddress::new(addr).context("Invalid address")?;
4720 let (contact_id, _) =
4721 Contact::add_or_lookup(self, "", &addr, Origin::Hidden).await?;
4722 match action {
4723 SyncAction::Block => {
4724 return contact::set_blocked(self, Nosync, contact_id, true).await;
4725 }
4726 SyncAction::Unblock => {
4727 return contact::set_blocked(self, Nosync, contact_id, false).await;
4728 }
4729 _ => (),
4730 }
4731 ChatIdBlocked::get_for_contact(self, contact_id, Blocked::Request)
4734 .await?
4735 .id
4736 }
4737 SyncId::ContactFingerprint(fingerprint) => {
4738 let name = "";
4739 let addr = "";
4740 let (contact_id, _) =
4741 Contact::add_or_lookup_ex(self, name, addr, fingerprint, Origin::Hidden)
4742 .await?;
4743 match action {
4744 SyncAction::Rename(to) => {
4745 contact_id.set_name_ex(self, Nosync, to).await?;
4746 self.emit_event(EventType::ContactsChanged(Some(contact_id)));
4747 return Ok(());
4748 }
4749 SyncAction::Block => {
4750 return contact::set_blocked(self, Nosync, contact_id, true).await;
4751 }
4752 SyncAction::Unblock => {
4753 return contact::set_blocked(self, Nosync, contact_id, false).await;
4754 }
4755 _ => (),
4756 }
4757 ChatIdBlocked::get_for_contact(self, contact_id, Blocked::Request)
4758 .await?
4759 .id
4760 }
4761 SyncId::Grpid(grpid) => {
4762 if let SyncAction::CreateBroadcast(name) = action {
4763 create_broadcast_ex(self, Nosync, grpid.clone(), name.clone()).await?;
4764 return Ok(());
4765 } else if let SyncAction::CreateGroupEncrypted(name) = action {
4766 create_group_ex(self, Nosync, grpid.clone(), name).await?;
4767 return Ok(());
4768 }
4769 get_chat_id_by_grpid(self, grpid)
4770 .await?
4771 .with_context(|| format!("No chat for grpid '{grpid}'"))?
4772 .0
4773 }
4774 SyncId::Msgids(msgids) => {
4775 let msg = message::get_by_rfc724_mids(self, msgids)
4776 .await?
4777 .with_context(|| format!("No message found for Message-IDs {msgids:?}"))?;
4778 ChatId::lookup_by_message(&msg)
4779 .with_context(|| format!("No chat found for Message-IDs {msgids:?}"))?
4780 }
4781 SyncId::Device => ChatId::get_for_contact(self, ContactId::DEVICE).await?,
4782 };
4783 match action {
4784 SyncAction::Block => chat_id.block_ex(self, Nosync).await,
4785 SyncAction::Unblock => chat_id.unblock_ex(self, Nosync).await,
4786 SyncAction::Accept => chat_id.accept_ex(self, Nosync).await,
4787 SyncAction::SetVisibility(v) => chat_id.set_visibility_ex(self, Nosync, *v).await,
4788 SyncAction::SetMuted(duration) => set_muted_ex(self, Nosync, chat_id, *duration).await,
4789 SyncAction::CreateBroadcast(_) | SyncAction::CreateGroupEncrypted(..) => {
4790 Err(anyhow!("sync_alter_chat({id:?}, {action:?}): Bad request."))
4791 }
4792 SyncAction::Rename(to) => rename_ex(self, Nosync, chat_id, to).await,
4793 SyncAction::SetContacts(addrs) => set_contacts_by_addrs(self, chat_id, addrs).await,
4794 SyncAction::SetPgpContacts(fingerprint_addrs) => {
4795 set_contacts_by_fingerprints(self, chat_id, fingerprint_addrs).await
4796 }
4797 SyncAction::Delete => chat_id.delete_ex(self, Nosync).await,
4798 }
4799 }
4800
4801 pub(crate) fn on_archived_chats_maybe_noticed(&self) {
4806 self.emit_msgs_changed_without_msg_id(DC_CHAT_ID_ARCHIVED_LINK);
4807 }
4808}
4809
4810#[cfg(test)]
4811mod chat_tests;