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, 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_broadcast_secret, create_id,
47 create_outgoing_rfc724_mid, create_smeared_timestamp, create_smeared_timestamps, get_abs_path,
48 gm2local_offset, normalize_text, smeared_time, time, truncate_msg_text,
49};
50use crate::webxdc::StatusUpdateSerial;
51use crate::{chatlist_events, imap};
52
53pub(crate) const PARAM_BROADCAST_SECRET: Param = Param::Arg3;
54
55#[derive(Debug, Copy, Clone, PartialEq, Eq)]
57pub enum ChatItem {
58 Message {
60 msg_id: MsgId,
62 },
63
64 DayMarker {
67 timestamp: i64,
69 },
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub(crate) enum CantSendReason {
77 SpecialChat,
79
80 DeviceChat,
82
83 ContactRequest,
85
86 ReadOnlyMailingList,
88
89 InBroadcast,
91
92 NotAMember,
94
95 MissingKey,
97}
98
99impl fmt::Display for CantSendReason {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 match self {
102 Self::SpecialChat => write!(f, "the chat is a special chat"),
103 Self::DeviceChat => write!(f, "the chat is a device chat"),
104 Self::ContactRequest => write!(
105 f,
106 "contact request chat should be accepted before sending messages"
107 ),
108 Self::ReadOnlyMailingList => {
109 write!(f, "mailing list does not have a know post address")
110 }
111 Self::InBroadcast => {
112 write!(f, "Broadcast channel is read-only")
113 }
114 Self::NotAMember => write!(f, "not a member of the chat"),
115 Self::MissingKey => write!(f, "key is missing"),
116 }
117 }
118}
119
120#[derive(
125 Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord,
126)]
127pub struct ChatId(u32);
128
129impl ChatId {
130 pub const fn new(id: u32) -> ChatId {
132 ChatId(id)
133 }
134
135 pub fn is_unset(self) -> bool {
139 self.0 == 0
140 }
141
142 pub fn is_special(self) -> bool {
146 (0..=DC_CHAT_ID_LAST_SPECIAL.0).contains(&self.0)
147 }
148
149 pub fn is_trash(self) -> bool {
156 self == DC_CHAT_ID_TRASH
157 }
158
159 pub fn is_archived_link(self) -> bool {
166 self == DC_CHAT_ID_ARCHIVED_LINK
167 }
168
169 pub fn is_alldone_hint(self) -> bool {
178 self == DC_CHAT_ID_ALLDONE_HINT
179 }
180
181 pub(crate) fn lookup_by_message(msg: &Message) -> Option<Self> {
183 if msg.chat_id == DC_CHAT_ID_TRASH {
184 return None;
185 }
186 if msg.download_state == DownloadState::Undecipherable {
187 return None;
188 }
189 Some(msg.chat_id)
190 }
191
192 pub async fn lookup_by_contact(
197 context: &Context,
198 contact_id: ContactId,
199 ) -> Result<Option<Self>> {
200 let Some(chat_id_blocked) = ChatIdBlocked::lookup_by_contact(context, contact_id).await?
201 else {
202 return Ok(None);
203 };
204
205 let chat_id = match chat_id_blocked.blocked {
206 Blocked::Not | Blocked::Request => Some(chat_id_blocked.id),
207 Blocked::Yes => None,
208 };
209 Ok(chat_id)
210 }
211
212 pub(crate) async fn get_for_contact(context: &Context, contact_id: ContactId) -> Result<Self> {
220 ChatIdBlocked::get_for_contact(context, contact_id, Blocked::Not)
221 .await
222 .map(|chat| chat.id)
223 }
224
225 pub async fn create_for_contact(context: &Context, contact_id: ContactId) -> Result<Self> {
230 ChatId::create_for_contact_with_blocked(context, contact_id, Blocked::Not).await
231 }
232
233 pub(crate) async fn create_for_contact_with_blocked(
237 context: &Context,
238 contact_id: ContactId,
239 create_blocked: Blocked,
240 ) -> Result<Self> {
241 let chat_id = match ChatIdBlocked::lookup_by_contact(context, contact_id).await? {
242 Some(chat) => {
243 if create_blocked != Blocked::Not || chat.blocked == Blocked::Not {
244 return Ok(chat.id);
245 }
246 chat.id.set_blocked(context, Blocked::Not).await?;
247 chat.id
248 }
249 None => {
250 if Contact::real_exists_by_id(context, contact_id).await?
251 || contact_id == ContactId::SELF
252 {
253 let chat_id =
254 ChatIdBlocked::get_for_contact(context, contact_id, create_blocked)
255 .await
256 .map(|chat| chat.id)?;
257 ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat).await?;
258 chat_id
259 } else {
260 warn!(
261 context,
262 "Cannot create chat, contact {contact_id} does not exist."
263 );
264 bail!("Can not create chat for non-existing contact");
265 }
266 }
267 };
268 context.emit_msgs_changed_without_ids();
269 chatlist_events::emit_chatlist_changed(context);
270 chatlist_events::emit_chatlist_item_changed(context, chat_id);
271 Ok(chat_id)
272 }
273
274 pub(crate) async fn create_multiuser_record(
277 context: &Context,
278 chattype: Chattype,
279 grpid: &str,
280 grpname: &str,
281 create_blocked: Blocked,
282 param: Option<String>,
283 timestamp: i64,
284 ) -> Result<Self> {
285 let grpname = sanitize_single_line(grpname);
286 let timestamp = cmp::min(timestamp, smeared_time(context));
287 let row_id =
288 context.sql.insert(
289 "INSERT INTO chats (type, name, name_normalized, grpid, blocked, created_timestamp, protected, param) VALUES(?, ?, ?, ?, ?, ?, 0, ?)",
290 (
291 chattype,
292 &grpname,
293 normalize_text(&grpname),
294 grpid,
295 create_blocked,
296 timestamp,
297 param.unwrap_or_default(),
298 ),
299 ).await?;
300
301 let chat_id = ChatId::new(u32::try_from(row_id)?);
302 let chat = Chat::load_from_db(context, chat_id).await?;
303
304 if chat.is_encrypted(context).await? {
305 chat_id.add_e2ee_notice(context, timestamp).await?;
306 }
307
308 info!(
309 context,
310 "Created group/broadcast '{}' grpid={} as {}, blocked={}.",
311 &grpname,
312 grpid,
313 chat_id,
314 create_blocked,
315 );
316
317 Ok(chat_id)
318 }
319
320 async fn set_selfavatar_timestamp(self, context: &Context, timestamp: i64) -> Result<()> {
321 context
322 .sql
323 .execute(
324 "UPDATE contacts
325 SET selfavatar_sent=?
326 WHERE id IN(SELECT contact_id FROM chats_contacts WHERE chat_id=? AND add_timestamp >= remove_timestamp)",
327 (timestamp, self),
328 )
329 .await?;
330 Ok(())
331 }
332
333 pub(crate) async fn set_blocked(self, context: &Context, new_blocked: Blocked) -> Result<bool> {
337 if self.is_special() {
338 bail!("ignoring setting of Block-status for {self}");
339 }
340 let count = context
341 .sql
342 .execute(
343 "UPDATE chats SET blocked=?1 WHERE id=?2 AND blocked != ?1",
344 (new_blocked, self),
345 )
346 .await?;
347 Ok(count > 0)
348 }
349
350 pub async fn block(self, context: &Context) -> Result<()> {
352 self.block_ex(context, Sync).await
353 }
354
355 pub(crate) async fn block_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
356 let chat = Chat::load_from_db(context, self).await?;
357 let mut delete = false;
358
359 match chat.typ {
360 Chattype::OutBroadcast => {
361 bail!("Can't block chat of type {:?}", chat.typ)
362 }
363 Chattype::Single => {
364 for contact_id in get_chat_contacts(context, self).await? {
365 if contact_id != ContactId::SELF {
366 info!(
367 context,
368 "Blocking the contact {contact_id} to block 1:1 chat."
369 );
370 contact::set_blocked(context, Nosync, contact_id, true).await?;
371 }
372 }
373 }
374 Chattype::Group => {
375 info!(context, "Can't block groups yet, deleting the chat.");
376 delete = true;
377 }
378 Chattype::Mailinglist | Chattype::InBroadcast => {
379 if self.set_blocked(context, Blocked::Yes).await? {
380 context.emit_event(EventType::ChatModified(self));
381 }
382 }
383 }
384 chatlist_events::emit_chatlist_changed(context);
385
386 if sync.into() {
387 chat.sync(context, SyncAction::Block)
389 .await
390 .log_err(context)
391 .ok();
392 }
393 if delete {
394 self.delete_ex(context, Nosync).await?;
395 }
396 Ok(())
397 }
398
399 pub async fn unblock(self, context: &Context) -> Result<()> {
401 self.unblock_ex(context, Sync).await
402 }
403
404 pub(crate) async fn unblock_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
405 self.set_blocked(context, Blocked::Not).await?;
406
407 chatlist_events::emit_chatlist_changed(context);
408
409 if sync.into() {
410 let chat = Chat::load_from_db(context, self).await?;
411 chat.sync(context, SyncAction::Unblock)
415 .await
416 .log_err(context)
417 .ok();
418 }
419
420 Ok(())
421 }
422
423 pub async fn accept(self, context: &Context) -> Result<()> {
427 self.accept_ex(context, Sync).await
428 }
429
430 pub(crate) async fn accept_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
431 let chat = Chat::load_from_db(context, self).await?;
432
433 match chat.typ {
434 Chattype::Single | Chattype::Group | Chattype::OutBroadcast | Chattype::InBroadcast => {
435 for contact_id in get_chat_contacts(context, self).await? {
440 if contact_id != ContactId::SELF {
441 ContactId::scaleup_origin(context, &[contact_id], Origin::CreateChat)
442 .await?;
443 }
444 }
445 }
446 Chattype::Mailinglist => {
447 }
449 }
450
451 if self.set_blocked(context, Blocked::Not).await? {
452 context.emit_event(EventType::ChatModified(self));
453 chatlist_events::emit_chatlist_item_changed(context, self);
454 }
455
456 if sync.into() {
457 chat.sync(context, SyncAction::Accept)
458 .await
459 .log_err(context)
460 .ok();
461 }
462 Ok(())
463 }
464
465 pub(crate) async fn add_e2ee_notice(self, context: &Context, timestamp: i64) -> Result<()> {
467 let text = stock_str::messages_e2e_encrypted(context).await;
468 add_info_msg_with_cmd(
469 context,
470 self,
471 &text,
472 SystemMessage::ChatE2ee,
473 Some(timestamp),
474 timestamp,
475 None,
476 None,
477 None,
478 )
479 .await?;
480 Ok(())
481 }
482
483 pub async fn set_visibility(self, context: &Context, visibility: ChatVisibility) -> Result<()> {
485 self.set_visibility_ex(context, Sync, visibility).await
486 }
487
488 pub(crate) async fn set_visibility_ex(
489 self,
490 context: &Context,
491 sync: sync::Sync,
492 visibility: ChatVisibility,
493 ) -> Result<()> {
494 ensure!(
495 !self.is_special(),
496 "bad chat_id, can not be special chat: {self}"
497 );
498
499 context
500 .sql
501 .transaction(move |transaction| {
502 if visibility == ChatVisibility::Archived {
503 transaction.execute(
504 "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;",
505 (MessageState::InNoticed, self, MessageState::InFresh),
506 )?;
507 }
508 transaction.execute(
509 "UPDATE chats SET archived=? WHERE id=?;",
510 (visibility, self),
511 )?;
512 Ok(())
513 })
514 .await?;
515
516 if visibility == ChatVisibility::Archived {
517 start_chat_ephemeral_timers(context, self).await?;
518 }
519
520 context.emit_msgs_changed_without_ids();
521 chatlist_events::emit_chatlist_changed(context);
522 chatlist_events::emit_chatlist_item_changed(context, self);
523
524 if sync.into() {
525 let chat = Chat::load_from_db(context, self).await?;
526 chat.sync(context, SyncAction::SetVisibility(visibility))
527 .await
528 .log_err(context)
529 .ok();
530 }
531 Ok(())
532 }
533
534 pub async fn unarchive_if_not_muted(
542 self,
543 context: &Context,
544 msg_state: MessageState,
545 ) -> Result<()> {
546 if msg_state != MessageState::InFresh {
547 context
548 .sql
549 .execute(
550 "UPDATE chats SET archived=0 WHERE id=? AND archived=1 \
551 AND NOT(muted_until=-1 OR muted_until>?)",
552 (self, time()),
553 )
554 .await?;
555 return Ok(());
556 }
557 let chat = Chat::load_from_db(context, self).await?;
558 if chat.visibility != ChatVisibility::Archived {
559 return Ok(());
560 }
561 if chat.is_muted() {
562 let unread_cnt = context
563 .sql
564 .count(
565 "SELECT COUNT(*)
566 FROM msgs
567 WHERE state=?
568 AND hidden=0
569 AND chat_id=?",
570 (MessageState::InFresh, self),
571 )
572 .await?;
573 if unread_cnt == 1 {
574 context.emit_msgs_changed_without_msg_id(DC_CHAT_ID_ARCHIVED_LINK);
576 }
577 return Ok(());
578 }
579 context
580 .sql
581 .execute("UPDATE chats SET archived=0 WHERE id=?", (self,))
582 .await?;
583 Ok(())
584 }
585
586 pub(crate) fn emit_msg_event(self, context: &Context, msg_id: MsgId, important: bool) {
589 if important {
590 debug_assert!(!msg_id.is_unset());
591
592 context.emit_incoming_msg(self, msg_id);
593 } else {
594 context.emit_msgs_changed(self, msg_id);
595 }
596 }
597
598 pub async fn delete(self, context: &Context) -> Result<()> {
600 self.delete_ex(context, Sync).await
601 }
602
603 pub(crate) async fn delete_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
604 ensure!(
605 !self.is_special(),
606 "bad chat_id, can not be a special chat: {self}"
607 );
608
609 let chat = Chat::load_from_db(context, self).await?;
610 let delete_msgs_target = context.get_delete_msgs_target().await?;
611 let sync_id = match sync {
612 Nosync => None,
613 Sync => chat.get_sync_id(context).await?,
614 };
615
616 context
617 .sql
618 .transaction(|transaction| {
619 transaction.execute(
620 "UPDATE imap SET target=? WHERE rfc724_mid IN (SELECT rfc724_mid FROM msgs WHERE chat_id=?)",
621 (delete_msgs_target, self,),
622 )?;
623 transaction.execute(
624 "DELETE FROM smtp WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?)",
625 (self,),
626 )?;
627 transaction.execute(
628 "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?)",
629 (self,),
630 )?;
631 transaction.execute("DELETE FROM msgs WHERE chat_id=?", (self,))?;
632 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (self,))?;
633 transaction.execute("DELETE FROM chats WHERE id=?", (self,))?;
634 Ok(())
635 })
636 .await?;
637
638 context.emit_event(EventType::ChatDeleted { chat_id: self });
639 context.emit_msgs_changed_without_ids();
640
641 if let Some(id) = sync_id {
642 self::sync(context, id, SyncAction::Delete)
643 .await
644 .log_err(context)
645 .ok();
646 }
647
648 if chat.is_self_talk() {
649 let mut msg = Message::new_text(stock_str::self_deleted_msg_body(context).await);
650 add_device_msg(context, None, Some(&mut msg)).await?;
651 }
652 chatlist_events::emit_chatlist_changed(context);
653
654 context
655 .set_config_internal(Config::LastHousekeeping, None)
656 .await?;
657 context.scheduler.interrupt_inbox().await;
658
659 Ok(())
660 }
661
662 pub async fn set_draft(self, context: &Context, mut msg: Option<&mut Message>) -> Result<()> {
666 if self.is_special() {
667 return Ok(());
668 }
669
670 let changed = match &mut msg {
671 None => self.maybe_delete_draft(context).await?,
672 Some(msg) => self.do_set_draft(context, msg).await?,
673 };
674
675 if changed {
676 if msg.is_some() {
677 match self.get_draft_msg_id(context).await? {
678 Some(msg_id) => context.emit_msgs_changed(self, msg_id),
679 None => context.emit_msgs_changed_without_msg_id(self),
680 }
681 } else {
682 context.emit_msgs_changed_without_msg_id(self)
683 }
684 }
685
686 Ok(())
687 }
688
689 async fn get_draft_msg_id(self, context: &Context) -> Result<Option<MsgId>> {
691 let msg_id: Option<MsgId> = context
692 .sql
693 .query_get_value(
694 "SELECT id FROM msgs WHERE chat_id=? AND state=?;",
695 (self, MessageState::OutDraft),
696 )
697 .await?;
698 Ok(msg_id)
699 }
700
701 pub async fn get_draft(self, context: &Context) -> Result<Option<Message>> {
703 if self.is_special() {
704 return Ok(None);
705 }
706 match self.get_draft_msg_id(context).await? {
707 Some(draft_msg_id) => {
708 let msg = Message::load_from_db(context, draft_msg_id).await?;
709 Ok(Some(msg))
710 }
711 None => Ok(None),
712 }
713 }
714
715 async fn maybe_delete_draft(self, context: &Context) -> Result<bool> {
719 Ok(context
720 .sql
721 .execute(
722 "DELETE FROM msgs WHERE chat_id=? AND state=?",
723 (self, MessageState::OutDraft),
724 )
725 .await?
726 > 0)
727 }
728
729 async fn do_set_draft(self, context: &Context, msg: &mut Message) -> Result<bool> {
732 match msg.viewtype {
733 Viewtype::Unknown => bail!("Can not set draft of unknown type."),
734 Viewtype::Text => {
735 if msg.text.is_empty() && msg.in_reply_to.is_none_or_empty() {
736 bail!("No text and no quote in draft");
737 }
738 }
739 _ => {
740 if msg.viewtype == Viewtype::File
741 && let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg)
742 .filter(|&(vt, _)| vt == Viewtype::Webxdc || vt == Viewtype::Vcard)
747 {
748 msg.viewtype = better_type;
749 }
750 if msg.viewtype == Viewtype::Vcard {
751 let blob = msg
752 .param
753 .get_file_blob(context)?
754 .context("no file stored in params")?;
755 msg.try_set_vcard(context, &blob.to_abs_path()).await?;
756 }
757 }
758 }
759
760 msg.state = MessageState::OutDraft;
763 msg.chat_id = self;
764
765 if !msg.id.is_special()
767 && let Some(old_draft) = self.get_draft(context).await?
768 && old_draft.id == msg.id
769 && old_draft.chat_id == self
770 && old_draft.state == MessageState::OutDraft
771 {
772 let affected_rows = context
773 .sql.execute(
774 "UPDATE msgs
775 SET timestamp=?1,type=?2,txt=?3,txt_normalized=?4,param=?5,mime_in_reply_to=?6
776 WHERE id=?7
777 AND (type <> ?2
778 OR txt <> ?3
779 OR txt_normalized <> ?4
780 OR param <> ?5
781 OR mime_in_reply_to <> ?6);",
782 (
783 time(),
784 msg.viewtype,
785 &msg.text,
786 normalize_text(&msg.text),
787 msg.param.to_string(),
788 msg.in_reply_to.as_deref().unwrap_or_default(),
789 msg.id,
790 ),
791 ).await?;
792 return Ok(affected_rows > 0);
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 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 && now > chat_timestamp + 42 * 24 * 3600
992 {
993 continue;
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 && last_msg_time > sort_timestamp
1251 {
1252 sort_timestamp = last_msg_time;
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 && let Ok(contact) = Contact::get_by_id(context, *contact_id).await
1375 {
1376 contact.get_display_name().clone_into(&mut chat_name);
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 | Chattype::InBroadcast => {
1493 is_contact_in_chat(context, self.id, ContactId::SELF).await
1494 }
1495 }
1496 }
1497
1498 pub(crate) async fn update_param(&mut self, context: &Context) -> Result<()> {
1499 context
1500 .sql
1501 .execute(
1502 "UPDATE chats SET param=? WHERE id=?",
1503 (self.param.to_string(), self.id),
1504 )
1505 .await?;
1506 Ok(())
1507 }
1508
1509 pub fn get_id(&self) -> ChatId {
1511 self.id
1512 }
1513
1514 pub fn get_type(&self) -> Chattype {
1516 self.typ
1517 }
1518
1519 pub fn get_name(&self) -> &str {
1521 &self.name
1522 }
1523
1524 pub fn get_mailinglist_addr(&self) -> Option<&str> {
1526 self.param.get(Param::ListPost)
1527 }
1528
1529 pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1531 if self.id.is_archived_link() {
1532 return Ok(Some(get_archive_icon(context).await?));
1535 } else if self.is_device_talk() {
1536 return Ok(Some(get_device_icon(context).await?));
1537 } else if self.is_self_talk() {
1538 return Ok(Some(get_saved_messages_icon(context).await?));
1539 } else if !self.is_encrypted(context).await? {
1540 return Ok(Some(get_abs_path(
1542 context,
1543 Path::new(&get_unencrypted_icon(context).await?),
1544 )));
1545 } else if self.typ == Chattype::Single {
1546 let contacts = get_chat_contacts(context, self.id).await?;
1550 if let Some(contact_id) = contacts.first() {
1551 let contact = Contact::get_by_id(context, *contact_id).await?;
1552 return contact.get_profile_image(context).await;
1553 }
1554 } else if let Some(image_rel) = self.param.get(Param::ProfileImage) {
1555 if !image_rel.is_empty() {
1557 return Ok(Some(get_abs_path(context, Path::new(&image_rel))));
1558 }
1559 }
1560 Ok(None)
1561 }
1562
1563 pub async fn get_color(&self, context: &Context) -> Result<u32> {
1569 let mut color = 0;
1570
1571 if self.typ == Chattype::Single {
1572 let contacts = get_chat_contacts(context, self.id).await?;
1573 if let Some(contact_id) = contacts.first()
1574 && let Ok(contact) = Contact::get_by_id(context, *contact_id).await
1575 {
1576 color = contact.get_color();
1577 }
1578 } else if !self.grpid.is_empty() {
1579 color = str_to_color(&self.grpid);
1580 } else {
1581 color = str_to_color(&self.name);
1582 }
1583
1584 Ok(color)
1585 }
1586
1587 pub async fn get_info(&self, context: &Context) -> Result<ChatInfo> {
1592 let draft = match self.id.get_draft(context).await? {
1593 Some(message) => message.text,
1594 _ => String::new(),
1595 };
1596 Ok(ChatInfo {
1597 id: self.id,
1598 type_: self.typ as u32,
1599 name: self.name.clone(),
1600 archived: self.visibility == ChatVisibility::Archived,
1601 param: self.param.to_string(),
1602 is_sending_locations: self.is_sending_locations,
1603 color: self.get_color(context).await?,
1604 profile_image: self
1605 .get_profile_image(context)
1606 .await?
1607 .unwrap_or_else(std::path::PathBuf::new),
1608 draft,
1609 is_muted: self.is_muted(),
1610 ephemeral_timer: self.id.get_ephemeral_timer(context).await?,
1611 })
1612 }
1613
1614 pub fn get_visibility(&self) -> ChatVisibility {
1616 self.visibility
1617 }
1618
1619 pub fn is_contact_request(&self) -> bool {
1624 self.blocked == Blocked::Request
1625 }
1626
1627 pub fn is_unpromoted(&self) -> bool {
1629 self.param.get_bool(Param::Unpromoted).unwrap_or_default()
1630 }
1631
1632 pub fn is_promoted(&self) -> bool {
1635 !self.is_unpromoted()
1636 }
1637
1638 pub async fn is_encrypted(&self, context: &Context) -> Result<bool> {
1640 let is_encrypted = self.is_self_talk()
1641 || match self.typ {
1642 Chattype::Single => {
1643 match context
1644 .sql
1645 .query_row_optional(
1646 "SELECT cc.contact_id, c.fingerprint<>''
1647 FROM chats_contacts cc LEFT JOIN contacts c
1648 ON c.id=cc.contact_id
1649 WHERE cc.chat_id=?
1650 ",
1651 (self.id,),
1652 |row| {
1653 let id: ContactId = row.get(0)?;
1654 let is_key: bool = row.get(1)?;
1655 Ok((id, is_key))
1656 },
1657 )
1658 .await?
1659 {
1660 Some((id, is_key)) => is_key || id == ContactId::DEVICE,
1661 None => true,
1662 }
1663 }
1664 Chattype::Group => {
1665 !self.grpid.is_empty()
1667 }
1668 Chattype::Mailinglist => false,
1669 Chattype::OutBroadcast | Chattype::InBroadcast => true,
1670 };
1671 Ok(is_encrypted)
1672 }
1673
1674 pub fn is_sending_locations(&self) -> bool {
1676 self.is_sending_locations
1677 }
1678
1679 pub fn is_muted(&self) -> bool {
1681 match self.mute_duration {
1682 MuteDuration::NotMuted => false,
1683 MuteDuration::Forever => true,
1684 MuteDuration::Until(when) => when > SystemTime::now(),
1685 }
1686 }
1687
1688 pub(crate) async fn member_list_timestamp(&self, context: &Context) -> Result<i64> {
1690 if let Some(member_list_timestamp) = self.param.get_i64(Param::MemberListTimestamp) {
1691 Ok(member_list_timestamp)
1692 } else {
1693 Ok(self.id.created_timestamp(context).await?)
1694 }
1695 }
1696
1697 pub(crate) async fn member_list_is_stale(&self, context: &Context) -> Result<bool> {
1703 let now = time();
1704 let member_list_ts = self.member_list_timestamp(context).await?;
1705 let is_stale = now.saturating_add(TIMESTAMP_SENT_TOLERANCE)
1706 >= member_list_ts.saturating_add(60 * 24 * 3600);
1707 Ok(is_stale)
1708 }
1709
1710 async fn prepare_msg_raw(
1716 &mut self,
1717 context: &Context,
1718 msg: &mut Message,
1719 update_msg_id: Option<MsgId>,
1720 ) -> Result<()> {
1721 let mut to_id = 0;
1722 let mut location_id = 0;
1723
1724 if msg.rfc724_mid.is_empty() {
1725 msg.rfc724_mid = create_outgoing_rfc724_mid();
1726 }
1727
1728 if self.typ == Chattype::Single {
1729 if let Some(id) = context
1730 .sql
1731 .query_get_value(
1732 "SELECT contact_id FROM chats_contacts WHERE chat_id=?;",
1733 (self.id,),
1734 )
1735 .await?
1736 {
1737 to_id = id;
1738 } else {
1739 error!(
1740 context,
1741 "Cannot send message, contact for {} not found.", self.id,
1742 );
1743 bail!("Cannot set message, contact for {} not found.", self.id);
1744 }
1745 } else if matches!(self.typ, Chattype::Group | Chattype::OutBroadcast)
1746 && self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
1747 {
1748 msg.param.set_int(Param::AttachGroupImage, 1);
1749 self.param
1750 .remove(Param::Unpromoted)
1751 .set_i64(Param::GroupNameTimestamp, msg.timestamp_sort);
1752 self.update_param(context).await?;
1753 context
1759 .sync_qr_code_tokens(Some(self.grpid.as_str()))
1760 .await
1761 .log_err(context)
1762 .ok();
1763 }
1764
1765 let is_bot = context.get_config_bool(Config::Bot).await?;
1766 msg.param
1767 .set_optional(Param::Bot, Some("1").filter(|_| is_bot));
1768
1769 let new_references;
1773 if self.is_self_talk() {
1774 new_references = String::new();
1777 } else if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
1778 self
1784 .id
1785 .get_parent_mime_headers(context, MessageState::OutPending)
1786 .await?
1787 {
1788 if msg.in_reply_to.is_none() && !parent_rfc724_mid.is_empty() {
1792 msg.in_reply_to = Some(parent_rfc724_mid.clone());
1793 }
1794
1795 let parent_references = if parent_references.is_empty() {
1805 parent_in_reply_to
1806 } else {
1807 parent_references
1808 };
1809
1810 let mut references_vec: Vec<&str> = parent_references.rsplit(' ').take(2).collect();
1813 references_vec.reverse();
1814
1815 if !parent_rfc724_mid.is_empty()
1816 && !references_vec.contains(&parent_rfc724_mid.as_str())
1817 {
1818 references_vec.push(&parent_rfc724_mid)
1819 }
1820
1821 if references_vec.is_empty() {
1822 new_references = msg.rfc724_mid.clone();
1825 } else {
1826 new_references = references_vec.join(" ");
1827 }
1828 } else {
1829 new_references = msg.rfc724_mid.clone();
1835 }
1836
1837 if msg.param.exists(Param::SetLatitude)
1839 && let Ok(row_id) = context
1840 .sql
1841 .insert(
1842 "INSERT INTO locations \
1843 (timestamp,from_id,chat_id, latitude,longitude,independent)\
1844 VALUES (?,?,?, ?,?,1);",
1845 (
1846 msg.timestamp_sort,
1847 ContactId::SELF,
1848 self.id,
1849 msg.param.get_float(Param::SetLatitude).unwrap_or_default(),
1850 msg.param.get_float(Param::SetLongitude).unwrap_or_default(),
1851 ),
1852 )
1853 .await
1854 {
1855 location_id = row_id;
1856 }
1857
1858 let ephemeral_timer = if msg.param.get_cmd() == SystemMessage::EphemeralTimerChanged {
1859 EphemeralTimer::Disabled
1860 } else {
1861 self.id.get_ephemeral_timer(context).await?
1862 };
1863 let ephemeral_timestamp = match ephemeral_timer {
1864 EphemeralTimer::Disabled => 0,
1865 EphemeralTimer::Enabled { duration } => time().saturating_add(duration.into()),
1866 };
1867
1868 let (msg_text, was_truncated) = truncate_msg_text(context, msg.text.clone()).await?;
1869 let new_mime_headers = if msg.has_html() {
1870 if msg.param.exists(Param::Forwarded) {
1871 msg.get_id().get_html(context).await?
1872 } else {
1873 msg.param.get(Param::SendHtml).map(|s| s.to_string())
1874 }
1875 } else {
1876 None
1877 };
1878 let new_mime_headers: Option<String> = new_mime_headers.map(|s| {
1879 let html_part = MimePart::new("text/html", s);
1880 let mut buffer = Vec::new();
1881 let cursor = Cursor::new(&mut buffer);
1882 html_part.write_part(cursor).ok();
1883 String::from_utf8_lossy(&buffer).to_string()
1884 });
1885 let new_mime_headers = new_mime_headers.or_else(|| match was_truncated {
1886 true => Some("Content-Type: text/plain; charset=utf-8\r\n\r\n".to_string() + &msg.text),
1890 false => None,
1891 });
1892 let new_mime_headers = match new_mime_headers {
1893 Some(h) => Some(tokio::task::block_in_place(move || {
1894 buf_compress(h.as_bytes())
1895 })?),
1896 None => None,
1897 };
1898
1899 msg.chat_id = self.id;
1900 msg.from_id = ContactId::SELF;
1901
1902 if let Some(update_msg_id) = update_msg_id {
1904 context
1905 .sql
1906 .execute(
1907 "UPDATE msgs
1908 SET rfc724_mid=?, chat_id=?, from_id=?, to_id=?, timestamp=?, type=?,
1909 state=?, txt=?, txt_normalized=?, subject=?, param=?,
1910 hidden=?, mime_in_reply_to=?, mime_references=?, mime_modified=?,
1911 mime_headers=?, mime_compressed=1, location_id=?, ephemeral_timer=?,
1912 ephemeral_timestamp=?
1913 WHERE id=?;",
1914 params_slice![
1915 msg.rfc724_mid,
1916 msg.chat_id,
1917 msg.from_id,
1918 to_id,
1919 msg.timestamp_sort,
1920 msg.viewtype,
1921 msg.state,
1922 msg_text,
1923 normalize_text(&msg_text),
1924 &msg.subject,
1925 msg.param.to_string(),
1926 msg.hidden,
1927 msg.in_reply_to.as_deref().unwrap_or_default(),
1928 new_references,
1929 new_mime_headers.is_some(),
1930 new_mime_headers.unwrap_or_default(),
1931 location_id as i32,
1932 ephemeral_timer,
1933 ephemeral_timestamp,
1934 update_msg_id
1935 ],
1936 )
1937 .await?;
1938 msg.id = update_msg_id;
1939 } else {
1940 let raw_id = context
1941 .sql
1942 .insert(
1943 "INSERT INTO msgs (
1944 rfc724_mid,
1945 chat_id,
1946 from_id,
1947 to_id,
1948 timestamp,
1949 type,
1950 state,
1951 txt,
1952 txt_normalized,
1953 subject,
1954 param,
1955 hidden,
1956 mime_in_reply_to,
1957 mime_references,
1958 mime_modified,
1959 mime_headers,
1960 mime_compressed,
1961 location_id,
1962 ephemeral_timer,
1963 ephemeral_timestamp)
1964 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,?,?,?);",
1965 params_slice![
1966 msg.rfc724_mid,
1967 msg.chat_id,
1968 msg.from_id,
1969 to_id,
1970 msg.timestamp_sort,
1971 msg.viewtype,
1972 msg.state,
1973 msg_text,
1974 normalize_text(&msg_text),
1975 &msg.subject,
1976 msg.param.to_string(),
1977 msg.hidden,
1978 msg.in_reply_to.as_deref().unwrap_or_default(),
1979 new_references,
1980 new_mime_headers.is_some(),
1981 new_mime_headers.unwrap_or_default(),
1982 location_id as i32,
1983 ephemeral_timer,
1984 ephemeral_timestamp
1985 ],
1986 )
1987 .await?;
1988 context.new_msgs_notify.notify_one();
1989 msg.id = MsgId::new(u32::try_from(raw_id)?);
1990
1991 maybe_set_logging_xdc(context, msg, self.id).await?;
1992 context
1993 .update_webxdc_integration_database(msg, context)
1994 .await?;
1995 }
1996 context.scheduler.interrupt_ephemeral_task().await;
1997 Ok(())
1998 }
1999
2000 pub(crate) async fn sync_contacts(&self, context: &Context) -> Result<()> {
2002 if self.is_encrypted(context).await? {
2003 let self_fp = self_fingerprint(context).await?;
2004 let fingerprint_addrs = context
2005 .sql
2006 .query_map_vec(
2007 "SELECT c.id, c.fingerprint, c.addr
2008 FROM contacts c INNER JOIN chats_contacts cc
2009 ON c.id=cc.contact_id
2010 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
2011 (self.id,),
2012 |row| {
2013 if row.get::<_, ContactId>(0)? == ContactId::SELF {
2014 return Ok((self_fp.to_string(), String::new()));
2015 }
2016 let fingerprint = row.get(1)?;
2017 let addr = row.get(2)?;
2018 Ok((fingerprint, addr))
2019 },
2020 )
2021 .await?;
2022 self.sync(context, SyncAction::SetPgpContacts(fingerprint_addrs))
2023 .await?;
2024 } else {
2025 let addrs = context
2026 .sql
2027 .query_map_vec(
2028 "SELECT c.addr \
2029 FROM contacts c INNER JOIN chats_contacts cc \
2030 ON c.id=cc.contact_id \
2031 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp",
2032 (self.id,),
2033 |row| {
2034 let addr: String = row.get(0)?;
2035 Ok(addr)
2036 },
2037 )
2038 .await?;
2039 self.sync(context, SyncAction::SetContacts(addrs)).await?;
2040 }
2041 Ok(())
2042 }
2043
2044 async fn get_sync_id(&self, context: &Context) -> Result<Option<SyncId>> {
2046 match self.typ {
2047 Chattype::Single => {
2048 if self.is_device_talk() {
2049 return Ok(Some(SyncId::Device));
2050 }
2051
2052 let mut r = None;
2053 for contact_id in get_chat_contacts(context, self.id).await? {
2054 if contact_id == ContactId::SELF && !self.is_self_talk() {
2055 continue;
2056 }
2057 if r.is_some() {
2058 return Ok(None);
2059 }
2060 let contact = Contact::get_by_id(context, contact_id).await?;
2061 if let Some(fingerprint) = contact.fingerprint() {
2062 r = Some(SyncId::ContactFingerprint(fingerprint.hex()));
2063 } else {
2064 r = Some(SyncId::ContactAddr(contact.get_addr().to_string()));
2065 }
2066 }
2067 Ok(r)
2068 }
2069 Chattype::OutBroadcast
2070 | Chattype::InBroadcast
2071 | Chattype::Group
2072 | Chattype::Mailinglist => {
2073 if !self.grpid.is_empty() {
2074 return Ok(Some(SyncId::Grpid(self.grpid.clone())));
2075 }
2076
2077 let Some((parent_rfc724_mid, parent_in_reply_to, _)) = self
2078 .id
2079 .get_parent_mime_headers(context, MessageState::OutDelivered)
2080 .await?
2081 else {
2082 warn!(
2083 context,
2084 "Chat::get_sync_id({}): No good message identifying the chat found.",
2085 self.id
2086 );
2087 return Ok(None);
2088 };
2089 Ok(Some(SyncId::Msgids(vec![
2090 parent_in_reply_to,
2091 parent_rfc724_mid,
2092 ])))
2093 }
2094 }
2095 }
2096
2097 pub(crate) async fn sync(&self, context: &Context, action: SyncAction) -> Result<()> {
2099 if let Some(id) = self.get_sync_id(context).await? {
2100 sync(context, id, action).await?;
2101 }
2102 Ok(())
2103 }
2104}
2105
2106pub(crate) async fn sync(context: &Context, id: SyncId, action: SyncAction) -> Result<()> {
2107 context
2108 .add_sync_item(SyncData::AlterChat { id, action })
2109 .await?;
2110 context.scheduler.interrupt_inbox().await;
2111 Ok(())
2112}
2113
2114#[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize, EnumIter)]
2116#[repr(i8)]
2117pub enum ChatVisibility {
2118 Normal = 0,
2120
2121 Archived = 1,
2123
2124 Pinned = 2,
2126}
2127
2128impl rusqlite::types::ToSql for ChatVisibility {
2129 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
2130 let val = rusqlite::types::Value::Integer(*self as i64);
2131 let out = rusqlite::types::ToSqlOutput::Owned(val);
2132 Ok(out)
2133 }
2134}
2135
2136impl rusqlite::types::FromSql for ChatVisibility {
2137 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
2138 i64::column_result(value).map(|val| {
2139 match val {
2140 2 => ChatVisibility::Pinned,
2141 1 => ChatVisibility::Archived,
2142 0 => ChatVisibility::Normal,
2143 _ => ChatVisibility::Normal,
2145 }
2146 })
2147 }
2148}
2149
2150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2152#[non_exhaustive]
2153pub struct ChatInfo {
2154 pub id: ChatId,
2156
2157 #[serde(rename = "type")]
2164 pub type_: u32,
2165
2166 pub name: String,
2168
2169 pub archived: bool,
2171
2172 pub param: String,
2176
2177 pub is_sending_locations: bool,
2179
2180 pub color: u32,
2184
2185 pub profile_image: std::path::PathBuf,
2190
2191 pub draft: String,
2199
2200 pub is_muted: bool,
2204
2205 pub ephemeral_timer: EphemeralTimer,
2207 }
2213
2214async fn get_asset_icon(context: &Context, name: &str, bytes: &[u8]) -> Result<PathBuf> {
2215 ensure!(name.starts_with("icon-"));
2216 if let Some(icon) = context.sql.get_raw_config(name).await? {
2217 return Ok(get_abs_path(context, Path::new(&icon)));
2218 }
2219
2220 let blob =
2221 BlobObject::create_and_deduplicate_from_bytes(context, bytes, &format!("{name}.png"))?;
2222 let icon = blob.as_name().to_string();
2223 context.sql.set_raw_config(name, Some(&icon)).await?;
2224
2225 Ok(get_abs_path(context, Path::new(&icon)))
2226}
2227
2228pub(crate) async fn get_saved_messages_icon(context: &Context) -> Result<PathBuf> {
2229 get_asset_icon(
2230 context,
2231 "icon-saved-messages",
2232 include_bytes!("../assets/icon-saved-messages.png"),
2233 )
2234 .await
2235}
2236
2237pub(crate) async fn get_device_icon(context: &Context) -> Result<PathBuf> {
2238 get_asset_icon(
2239 context,
2240 "icon-device",
2241 include_bytes!("../assets/icon-device.png"),
2242 )
2243 .await
2244}
2245
2246pub(crate) async fn get_archive_icon(context: &Context) -> Result<PathBuf> {
2247 get_asset_icon(
2248 context,
2249 "icon-archive",
2250 include_bytes!("../assets/icon-archive.png"),
2251 )
2252 .await
2253}
2254
2255pub(crate) async fn get_unencrypted_icon(context: &Context) -> Result<PathBuf> {
2258 get_asset_icon(
2259 context,
2260 "icon-unencrypted",
2261 include_bytes!("../assets/icon-unencrypted.png"),
2262 )
2263 .await
2264}
2265
2266async fn update_special_chat_name(
2267 context: &Context,
2268 contact_id: ContactId,
2269 name: String,
2270) -> Result<()> {
2271 if let Some(ChatIdBlocked { id: chat_id, .. }) =
2272 ChatIdBlocked::lookup_by_contact(context, contact_id).await?
2273 {
2274 context
2276 .sql
2277 .execute(
2278 "UPDATE chats SET name=?, name_normalized=? WHERE id=? AND name!=?",
2279 (&name, normalize_text(&name), chat_id, &name),
2280 )
2281 .await?;
2282 }
2283 Ok(())
2284}
2285
2286pub(crate) async fn update_special_chat_names(context: &Context) -> Result<()> {
2287 update_special_chat_name(
2288 context,
2289 ContactId::DEVICE,
2290 stock_str::device_messages(context).await,
2291 )
2292 .await?;
2293 update_special_chat_name(
2294 context,
2295 ContactId::SELF,
2296 stock_str::saved_messages(context).await,
2297 )
2298 .await?;
2299 Ok(())
2300}
2301
2302#[derive(Debug)]
2310pub(crate) struct ChatIdBlocked {
2311 pub id: ChatId,
2313
2314 pub blocked: Blocked,
2316}
2317
2318impl ChatIdBlocked {
2319 pub async fn lookup_by_contact(
2323 context: &Context,
2324 contact_id: ContactId,
2325 ) -> Result<Option<Self>> {
2326 ensure!(context.sql.is_open().await, "Database not available");
2327 ensure!(
2328 contact_id != ContactId::UNDEFINED,
2329 "Invalid contact id requested"
2330 );
2331
2332 context
2333 .sql
2334 .query_row_optional(
2335 "SELECT c.id, c.blocked
2336 FROM chats c
2337 INNER JOIN chats_contacts j
2338 ON c.id=j.chat_id
2339 WHERE c.type=100 -- 100 = Chattype::Single
2340 AND c.id>9 -- 9 = DC_CHAT_ID_LAST_SPECIAL
2341 AND j.contact_id=?;",
2342 (contact_id,),
2343 |row| {
2344 let id: ChatId = row.get(0)?;
2345 let blocked: Blocked = row.get(1)?;
2346 Ok(ChatIdBlocked { id, blocked })
2347 },
2348 )
2349 .await
2350 }
2351
2352 pub async fn get_for_contact(
2357 context: &Context,
2358 contact_id: ContactId,
2359 create_blocked: Blocked,
2360 ) -> Result<Self> {
2361 ensure!(context.sql.is_open().await, "Database not available");
2362 ensure!(
2363 contact_id != ContactId::UNDEFINED,
2364 "Invalid contact id requested"
2365 );
2366
2367 if let Some(res) = Self::lookup_by_contact(context, contact_id).await? {
2368 return Ok(res);
2370 }
2371
2372 let contact = Contact::get_by_id(context, contact_id).await?;
2373 let chat_name = contact.get_display_name().to_string();
2374 let mut params = Params::new();
2375 match contact_id {
2376 ContactId::SELF => {
2377 params.set_int(Param::Selftalk, 1);
2378 }
2379 ContactId::DEVICE => {
2380 params.set_int(Param::Devicetalk, 1);
2381 }
2382 _ => (),
2383 }
2384
2385 let smeared_time = create_smeared_timestamp(context);
2386
2387 let chat_id = context
2388 .sql
2389 .transaction(move |transaction| {
2390 transaction.execute(
2391 "INSERT INTO chats
2392 (type, name, name_normalized, param, blocked, created_timestamp)
2393 VALUES(?, ?, ?, ?, ?, ?)",
2394 (
2395 Chattype::Single,
2396 &chat_name,
2397 normalize_text(&chat_name),
2398 params.to_string(),
2399 create_blocked as u8,
2400 smeared_time,
2401 ),
2402 )?;
2403 let chat_id = ChatId::new(
2404 transaction
2405 .last_insert_rowid()
2406 .try_into()
2407 .context("chat table rowid overflows u32")?,
2408 );
2409
2410 transaction.execute(
2411 "INSERT INTO chats_contacts
2412 (chat_id, contact_id)
2413 VALUES((SELECT last_insert_rowid()), ?)",
2414 (contact_id,),
2415 )?;
2416
2417 Ok(chat_id)
2418 })
2419 .await?;
2420
2421 let chat = Chat::load_from_db(context, chat_id).await?;
2422 if chat.is_encrypted(context).await?
2423 && !chat.param.exists(Param::Devicetalk)
2424 && !chat.param.exists(Param::Selftalk)
2425 {
2426 chat_id.add_e2ee_notice(context, smeared_time).await?;
2427 }
2428
2429 Ok(Self {
2430 id: chat_id,
2431 blocked: create_blocked,
2432 })
2433 }
2434}
2435
2436async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> {
2437 if msg.viewtype == Viewtype::Text || msg.viewtype == Viewtype::Call {
2438 } else if msg.viewtype.has_file() {
2440 let viewtype_orig = msg.viewtype;
2441 let mut blob = msg
2442 .param
2443 .get_file_blob(context)?
2444 .with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?;
2445 let mut maybe_image = false;
2446
2447 if msg.viewtype == Viewtype::File
2448 || msg.viewtype == Viewtype::Image
2449 || msg.viewtype == Viewtype::Sticker && !msg.param.exists(Param::ForceSticker)
2450 {
2451 if let Some((better_type, _)) = message::guess_msgtype_from_suffix(msg) {
2458 if msg.viewtype == Viewtype::Sticker {
2459 if better_type != Viewtype::Image {
2460 msg.param.set_int(Param::ForceSticker, 1);
2462 }
2463 } else if better_type == Viewtype::Image {
2464 maybe_image = true;
2465 } else if better_type != Viewtype::Webxdc
2466 || context
2467 .ensure_sendable_webxdc_file(&blob.to_abs_path())
2468 .await
2469 .is_ok()
2470 {
2471 msg.viewtype = better_type;
2472 }
2473 }
2474 } else if msg.viewtype == Viewtype::Webxdc {
2475 context
2476 .ensure_sendable_webxdc_file(&blob.to_abs_path())
2477 .await?;
2478 }
2479
2480 if msg.viewtype == Viewtype::Vcard {
2481 msg.try_set_vcard(context, &blob.to_abs_path()).await?;
2482 }
2483 if msg.viewtype == Viewtype::File && maybe_image
2484 || msg.viewtype == Viewtype::Image
2485 || msg.viewtype == Viewtype::Sticker && !msg.param.exists(Param::ForceSticker)
2486 {
2487 let new_name = blob
2488 .check_or_recode_image(context, msg.get_filename(), &mut msg.viewtype)
2489 .await?;
2490 msg.param.set(Param::Filename, new_name);
2491 msg.param.set(Param::File, blob.as_name());
2492 }
2493
2494 if !msg.param.exists(Param::MimeType)
2495 && let Some((viewtype, mime)) = message::guess_msgtype_from_suffix(msg)
2496 {
2497 let mime = match viewtype != Viewtype::Image
2500 || matches!(msg.viewtype, Viewtype::Image | Viewtype::Sticker)
2501 {
2502 true => mime,
2503 false => "application/octet-stream",
2504 };
2505 msg.param.set(Param::MimeType, mime);
2506 }
2507
2508 msg.try_calc_and_set_dimensions(context).await?;
2509
2510 let filename = msg.get_filename().context("msg has no file")?;
2511 let suffix = Path::new(&filename)
2512 .extension()
2513 .and_then(|e| e.to_str())
2514 .unwrap_or("dat");
2515 let filename: String = match viewtype_orig {
2519 Viewtype::Voice => format!(
2520 "voice-messsage_{}.{}",
2521 chrono::Utc
2522 .timestamp_opt(msg.timestamp_sort, 0)
2523 .single()
2524 .map_or_else(
2525 || "YY-mm-dd_hh:mm:ss".to_string(),
2526 |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string()
2527 ),
2528 &suffix
2529 ),
2530 Viewtype::Image | Viewtype::Gif => format!(
2531 "image_{}.{}",
2532 chrono::Utc
2533 .timestamp_opt(msg.timestamp_sort, 0)
2534 .single()
2535 .map_or_else(
2536 || "YY-mm-dd_hh:mm:ss".to_string(),
2537 |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string(),
2538 ),
2539 &suffix,
2540 ),
2541 Viewtype::Video => format!(
2542 "video_{}.{}",
2543 chrono::Utc
2544 .timestamp_opt(msg.timestamp_sort, 0)
2545 .single()
2546 .map_or_else(
2547 || "YY-mm-dd_hh:mm:ss".to_string(),
2548 |ts| ts.format("%Y-%m-%d_%H-%M-%S").to_string()
2549 ),
2550 &suffix
2551 ),
2552 _ => filename,
2553 };
2554 msg.param.set(Param::Filename, filename);
2555
2556 info!(
2557 context,
2558 "Attaching \"{}\" for message type #{}.",
2559 blob.to_abs_path().display(),
2560 msg.viewtype
2561 );
2562 } else {
2563 bail!("Cannot send messages of type #{}.", msg.viewtype);
2564 }
2565 Ok(())
2566}
2567
2568pub async fn is_contact_in_chat(
2570 context: &Context,
2571 chat_id: ChatId,
2572 contact_id: ContactId,
2573) -> Result<bool> {
2574 let exists = context
2581 .sql
2582 .exists(
2583 "SELECT COUNT(*) FROM chats_contacts
2584 WHERE chat_id=? AND contact_id=?
2585 AND add_timestamp >= remove_timestamp",
2586 (chat_id, contact_id),
2587 )
2588 .await?;
2589 Ok(exists)
2590}
2591
2592pub async fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2599 ensure!(
2600 !chat_id.is_special(),
2601 "chat_id cannot be a special chat: {chat_id}"
2602 );
2603
2604 if msg.state != MessageState::Undefined && msg.state != MessageState::OutPreparing {
2605 msg.param.remove(Param::GuaranteeE2ee);
2606 msg.param.remove(Param::ForcePlaintext);
2607 msg.update_param(context).await?;
2608 }
2609
2610 if msg.is_system_message() {
2612 msg.text = sanitize_bidi_characters(&msg.text);
2613 }
2614
2615 if !prepare_send_msg(context, chat_id, msg).await?.is_empty() {
2616 if !msg.hidden {
2617 context.emit_msgs_changed(msg.chat_id, msg.id);
2618 }
2619
2620 if msg.param.exists(Param::SetLatitude) {
2621 context.emit_location_changed(Some(ContactId::SELF)).await?;
2622 }
2623
2624 context.scheduler.interrupt_smtp().await;
2625 }
2626
2627 Ok(msg.id)
2628}
2629
2630pub async fn send_msg_sync(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
2635 let rowids = prepare_send_msg(context, chat_id, msg).await?;
2636 if rowids.is_empty() {
2637 return Ok(msg.id);
2638 }
2639 let mut smtp = crate::smtp::Smtp::new();
2640 for rowid in rowids {
2641 send_msg_to_smtp(context, &mut smtp, rowid)
2642 .await
2643 .context("failed to send message, queued for later sending")?;
2644 }
2645 context.emit_msgs_changed(msg.chat_id, msg.id);
2646 Ok(msg.id)
2647}
2648
2649async fn prepare_send_msg(
2653 context: &Context,
2654 chat_id: ChatId,
2655 msg: &mut Message,
2656) -> Result<Vec<i64>> {
2657 let mut chat = Chat::load_from_db(context, chat_id).await?;
2658
2659 let skip_fn = |reason: &CantSendReason| match reason {
2660 CantSendReason::ContactRequest => {
2661 msg.param.get_cmd() == SystemMessage::SecurejoinMessage
2664 }
2665 CantSendReason::NotAMember => msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup,
2669 CantSendReason::InBroadcast => {
2670 matches!(
2671 msg.param.get_cmd(),
2672 SystemMessage::MemberRemovedFromGroup | SystemMessage::SecurejoinMessage
2673 )
2674 }
2675 CantSendReason::MissingKey => msg
2676 .param
2677 .get_bool(Param::ForcePlaintext)
2678 .unwrap_or_default(),
2679 _ => false,
2680 };
2681 if let Some(reason) = chat.why_cant_send_ex(context, &skip_fn).await? {
2682 bail!("Cannot send to {chat_id}: {reason}");
2683 }
2684
2685 if chat.typ != Chattype::Single
2690 && !context.get_config_bool(Config::Bot).await?
2691 && let Some(quoted_message) = msg.quoted_message(context).await?
2692 && quoted_message.chat_id != chat_id
2693 {
2694 bail!(
2695 "Quote of message from {} cannot be sent to {chat_id}",
2696 quoted_message.chat_id
2697 );
2698 }
2699
2700 let update_msg_id = if msg.state == MessageState::OutDraft {
2702 msg.hidden = false;
2703 if !msg.id.is_special() && msg.chat_id == chat_id {
2704 Some(msg.id)
2705 } else {
2706 None
2707 }
2708 } else {
2709 None
2710 };
2711
2712 msg.state = MessageState::OutPending;
2714
2715 msg.timestamp_sort = create_smeared_timestamp(context);
2716 prepare_msg_blob(context, msg).await?;
2717 if !msg.hidden {
2718 chat_id.unarchive_if_not_muted(context, msg.state).await?;
2719 }
2720 chat.prepare_msg_raw(context, msg, update_msg_id).await?;
2721
2722 let row_ids = create_send_msg_jobs(context, msg)
2723 .await
2724 .context("Failed to create send jobs")?;
2725 if !row_ids.is_empty() {
2726 donation_request_maybe(context).await.log_err(context).ok();
2727 }
2728 Ok(row_ids)
2729}
2730
2731pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -> Result<Vec<i64>> {
2741 if msg.param.get_cmd() == SystemMessage::GroupNameChanged {
2742 msg.chat_id
2743 .update_timestamp(context, Param::GroupNameTimestamp, msg.timestamp_sort)
2744 .await?;
2745 }
2746
2747 let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
2748 let mimefactory = match MimeFactory::from_msg(context, msg.clone()).await {
2749 Ok(mf) => mf,
2750 Err(err) => {
2751 message::set_msg_failed(context, msg, &err.to_string())
2753 .await
2754 .ok();
2755 return Err(err);
2756 }
2757 };
2758 let attach_selfavatar = mimefactory.attach_selfavatar;
2759 let mut recipients = mimefactory.recipients();
2760
2761 let from = context.get_primary_self_addr().await?;
2762 let lowercase_from = from.to_lowercase();
2763
2764 recipients.retain(|x| x.to_lowercase() != lowercase_from);
2777 if (context.get_config_bool(Config::BccSelf).await?
2778 || msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage)
2779 && (context.get_config_delete_server_after().await? != Some(0) || !recipients.is_empty())
2780 {
2781 recipients.push(from);
2782 }
2783
2784 if msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden {
2786 recipients.clear();
2787 }
2788
2789 if recipients.is_empty() {
2790 info!(
2792 context,
2793 "Message {} has no recipient, skipping smtp-send.", msg.id
2794 );
2795 msg.param.set_int(Param::GuaranteeE2ee, 1);
2796 msg.update_param(context).await?;
2797 msg.id.set_delivered(context).await?;
2798 msg.state = MessageState::OutDelivered;
2799 return Ok(Vec::new());
2800 }
2801
2802 let rendered_msg = match mimefactory.render(context).await {
2803 Ok(res) => Ok(res),
2804 Err(err) => {
2805 message::set_msg_failed(context, msg, &err.to_string()).await?;
2806 Err(err)
2807 }
2808 }?;
2809
2810 if needs_encryption && !rendered_msg.is_encrypted {
2811 message::set_msg_failed(
2813 context,
2814 msg,
2815 "End-to-end-encryption unavailable unexpectedly.",
2816 )
2817 .await?;
2818 bail!(
2819 "e2e encryption unavailable {} - {:?}",
2820 msg.id,
2821 needs_encryption
2822 );
2823 }
2824
2825 let now = smeared_time(context);
2826
2827 if rendered_msg.last_added_location_id.is_some()
2828 && let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, now).await
2829 {
2830 error!(context, "Failed to set kml sent_timestamp: {err:#}.");
2831 }
2832
2833 if attach_selfavatar && let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, now).await
2834 {
2835 error!(context, "Failed to set selfavatar timestamp: {err:#}.");
2836 }
2837
2838 if rendered_msg.is_encrypted {
2839 msg.param.set_int(Param::GuaranteeE2ee, 1);
2840 } else {
2841 msg.param.remove(Param::GuaranteeE2ee);
2842 }
2843 msg.subject.clone_from(&rendered_msg.subject);
2844 context
2845 .sql
2846 .execute(
2847 "UPDATE msgs SET subject=?, param=? WHERE id=?",
2848 (&msg.subject, msg.param.to_string(), msg.id),
2849 )
2850 .await?;
2851
2852 let chunk_size = context.get_max_smtp_rcpt_to().await?;
2853 let trans_fn = |t: &mut rusqlite::Transaction| {
2854 let mut row_ids = Vec::<i64>::new();
2855 if let Some(sync_ids) = rendered_msg.sync_ids_to_delete {
2856 t.execute(
2857 &format!("DELETE FROM multi_device_sync WHERE id IN ({sync_ids})"),
2858 (),
2859 )?;
2860 t.execute(
2861 "INSERT INTO imap_send (mime, msg_id) VALUES (?, ?)",
2862 (&rendered_msg.message, msg.id),
2863 )?;
2864 } else {
2865 for recipients_chunk in recipients.chunks(chunk_size) {
2866 let recipients_chunk = recipients_chunk.join(" ");
2867 let row_id = t.execute(
2868 "INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id) \
2869 VALUES (?1, ?2, ?3, ?4)",
2870 (
2871 &rendered_msg.rfc724_mid,
2872 recipients_chunk,
2873 &rendered_msg.message,
2874 msg.id,
2875 ),
2876 )?;
2877 row_ids.push(row_id.try_into()?);
2878 }
2879 }
2880 Ok(row_ids)
2881 };
2882 context.sql.transaction(trans_fn).await
2883}
2884
2885pub async fn send_text_msg(
2889 context: &Context,
2890 chat_id: ChatId,
2891 text_to_send: String,
2892) -> Result<MsgId> {
2893 ensure!(
2894 !chat_id.is_special(),
2895 "bad chat_id, can not be a special chat: {chat_id}"
2896 );
2897
2898 let mut msg = Message::new_text(text_to_send);
2899 send_msg(context, chat_id, &mut msg).await
2900}
2901
2902pub async fn send_edit_request(context: &Context, msg_id: MsgId, new_text: String) -> Result<()> {
2904 let mut original_msg = Message::load_from_db(context, msg_id).await?;
2905 ensure!(
2906 original_msg.from_id == ContactId::SELF,
2907 "Can edit only own messages"
2908 );
2909 ensure!(!original_msg.is_info(), "Cannot edit info messages");
2910 ensure!(!original_msg.has_html(), "Cannot edit HTML messages");
2911 ensure!(original_msg.viewtype != Viewtype::Call, "Cannot edit calls");
2912 ensure!(
2913 !original_msg.text.is_empty(), "Cannot add text"
2915 );
2916 ensure!(!new_text.trim().is_empty(), "Edited text cannot be empty");
2917 if original_msg.text == new_text {
2918 info!(context, "Text unchanged.");
2919 return Ok(());
2920 }
2921
2922 save_text_edit_to_db(context, &mut original_msg, &new_text).await?;
2923
2924 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() {
2927 edit_msg.param.set_int(Param::GuaranteeE2ee, 1);
2928 }
2929 edit_msg
2930 .param
2931 .set(Param::TextEditFor, original_msg.rfc724_mid);
2932 edit_msg.hidden = true;
2933 send_msg(context, original_msg.chat_id, &mut edit_msg).await?;
2934 Ok(())
2935}
2936
2937pub(crate) async fn save_text_edit_to_db(
2938 context: &Context,
2939 original_msg: &mut Message,
2940 new_text: &str,
2941) -> Result<()> {
2942 original_msg.param.set_int(Param::IsEdited, 1);
2943 context
2944 .sql
2945 .execute(
2946 "UPDATE msgs SET txt=?, txt_normalized=?, param=? WHERE id=?",
2947 (
2948 new_text,
2949 normalize_text(new_text),
2950 original_msg.param.to_string(),
2951 original_msg.id,
2952 ),
2953 )
2954 .await?;
2955 context.emit_msgs_changed(original_msg.chat_id, original_msg.id);
2956 Ok(())
2957}
2958
2959async fn donation_request_maybe(context: &Context) -> Result<()> {
2960 let secs_between_checks = 30 * 24 * 60 * 60;
2961 let now = time();
2962 let ts = context
2963 .get_config_i64(Config::DonationRequestNextCheck)
2964 .await?;
2965 if ts > now {
2966 return Ok(());
2967 }
2968 let msg_cnt = context.sql.count(
2969 "SELECT COUNT(*) FROM msgs WHERE state>=? AND hidden=0",
2970 (MessageState::OutDelivered,),
2971 );
2972 let ts = if ts == 0 || msg_cnt.await? < 100 {
2973 now.saturating_add(secs_between_checks)
2974 } else {
2975 let mut msg = Message::new_text(stock_str::donation_request(context).await);
2976 add_device_msg(context, None, Some(&mut msg)).await?;
2977 i64::MAX
2978 };
2979 context
2980 .set_config_internal(Config::DonationRequestNextCheck, Some(&ts.to_string()))
2981 .await
2982}
2983
2984#[derive(Debug)]
2986pub struct MessageListOptions {
2987 pub info_only: bool,
2989
2990 pub add_daymarker: bool,
2992}
2993
2994pub async fn get_chat_msgs(context: &Context, chat_id: ChatId) -> Result<Vec<ChatItem>> {
2996 get_chat_msgs_ex(
2997 context,
2998 chat_id,
2999 MessageListOptions {
3000 info_only: false,
3001 add_daymarker: false,
3002 },
3003 )
3004 .await
3005}
3006
3007pub async fn get_chat_msgs_ex(
3009 context: &Context,
3010 chat_id: ChatId,
3011 options: MessageListOptions,
3012) -> Result<Vec<ChatItem>> {
3013 let MessageListOptions {
3014 info_only,
3015 add_daymarker,
3016 } = options;
3017 let process_row = if info_only {
3018 |row: &rusqlite::Row| {
3019 let params = row.get::<_, String>("param")?;
3021 let (from_id, to_id) = (
3022 row.get::<_, ContactId>("from_id")?,
3023 row.get::<_, ContactId>("to_id")?,
3024 );
3025 let is_info_msg: bool = from_id == ContactId::INFO
3026 || to_id == ContactId::INFO
3027 || match Params::from_str(¶ms) {
3028 Ok(p) => {
3029 let cmd = p.get_cmd();
3030 cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
3031 }
3032 _ => false,
3033 };
3034
3035 Ok((
3036 row.get::<_, i64>("timestamp")?,
3037 row.get::<_, MsgId>("id")?,
3038 !is_info_msg,
3039 ))
3040 }
3041 } else {
3042 |row: &rusqlite::Row| {
3043 Ok((
3044 row.get::<_, i64>("timestamp")?,
3045 row.get::<_, MsgId>("id")?,
3046 false,
3047 ))
3048 }
3049 };
3050 let process_rows = |rows: rusqlite::AndThenRows<_>| {
3051 let mut sorted_rows = Vec::new();
3054 for row in rows {
3055 let (ts, curr_id, exclude_message): (i64, MsgId, bool) = row?;
3056 if !exclude_message {
3057 sorted_rows.push((ts, curr_id));
3058 }
3059 }
3060 sorted_rows.sort_unstable();
3061
3062 let mut ret = Vec::new();
3063 let mut last_day = 0;
3064 let cnv_to_local = gm2local_offset();
3065
3066 for (ts, curr_id) in sorted_rows {
3067 if add_daymarker {
3068 let curr_local_timestamp = ts + cnv_to_local;
3069 let secs_in_day = 86400;
3070 let curr_day = curr_local_timestamp / secs_in_day;
3071 if curr_day != last_day {
3072 ret.push(ChatItem::DayMarker {
3073 timestamp: curr_day * secs_in_day - cnv_to_local,
3074 });
3075 last_day = curr_day;
3076 }
3077 }
3078 ret.push(ChatItem::Message { msg_id: curr_id });
3079 }
3080 Ok(ret)
3081 };
3082
3083 let items = if info_only {
3084 context
3085 .sql
3086 .query_map(
3087 "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
3089 FROM msgs m
3090 WHERE m.chat_id=?
3091 AND m.hidden=0
3092 AND (
3093 m.param GLOB \"*S=*\"
3094 OR m.from_id == ?
3095 OR m.to_id == ?
3096 );",
3097 (chat_id, ContactId::INFO, ContactId::INFO),
3098 process_row,
3099 process_rows,
3100 )
3101 .await?
3102 } else {
3103 context
3104 .sql
3105 .query_map(
3106 "SELECT m.id AS id, m.timestamp AS timestamp
3107 FROM msgs m
3108 WHERE m.chat_id=?
3109 AND m.hidden=0;",
3110 (chat_id,),
3111 process_row,
3112 process_rows,
3113 )
3114 .await?
3115 };
3116 Ok(items)
3117}
3118
3119pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()> {
3122 if chat_id.is_archived_link() {
3125 let chat_ids_in_archive = context
3126 .sql
3127 .query_map_vec(
3128 "SELECT DISTINCT(m.chat_id) FROM msgs m
3129 LEFT JOIN chats c ON m.chat_id=c.id
3130 WHERE m.state=10 AND m.hidden=0 AND m.chat_id>9 AND c.archived=1",
3131 (),
3132 |row| {
3133 let chat_id: ChatId = row.get(0)?;
3134 Ok(chat_id)
3135 },
3136 )
3137 .await?;
3138 if chat_ids_in_archive.is_empty() {
3139 return Ok(());
3140 }
3141
3142 context
3143 .sql
3144 .transaction(|transaction| {
3145 let mut stmt = transaction.prepare(
3146 "UPDATE msgs SET state=13 WHERE state=10 AND hidden=0 AND chat_id = ?",
3147 )?;
3148 for chat_id_in_archive in &chat_ids_in_archive {
3149 stmt.execute((chat_id_in_archive,))?;
3150 }
3151 Ok(())
3152 })
3153 .await?;
3154
3155 for chat_id_in_archive in chat_ids_in_archive {
3156 start_chat_ephemeral_timers(context, chat_id_in_archive).await?;
3157 context.emit_event(EventType::MsgsNoticed(chat_id_in_archive));
3158 chatlist_events::emit_chatlist_item_changed(context, chat_id_in_archive);
3159 }
3160 } else {
3161 start_chat_ephemeral_timers(context, chat_id).await?;
3162
3163 let noticed_msgs_count = context
3164 .sql
3165 .execute(
3166 "UPDATE msgs
3167 SET state=?
3168 WHERE state=?
3169 AND hidden=0
3170 AND chat_id=?;",
3171 (MessageState::InNoticed, MessageState::InFresh, chat_id),
3172 )
3173 .await?;
3174
3175 let hidden_messages = context
3178 .sql
3179 .query_map_vec(
3180 "SELECT id, rfc724_mid FROM msgs
3181 WHERE state=?
3182 AND hidden=1
3183 AND chat_id=?
3184 ORDER BY id LIMIT 100", (MessageState::InFresh, chat_id), |row| {
3187 let msg_id: MsgId = row.get(0)?;
3188 let rfc724_mid: String = row.get(1)?;
3189 Ok((msg_id, rfc724_mid))
3190 },
3191 )
3192 .await?;
3193 for (msg_id, rfc724_mid) in &hidden_messages {
3194 message::update_msg_state(context, *msg_id, MessageState::InSeen).await?;
3195 imap::markseen_on_imap_table(context, rfc724_mid).await?;
3196 }
3197
3198 if noticed_msgs_count == 0 {
3199 return Ok(());
3200 }
3201 }
3202
3203 context.emit_event(EventType::MsgsNoticed(chat_id));
3204 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3205 context.on_archived_chats_maybe_noticed();
3206 Ok(())
3207}
3208
3209pub(crate) async fn mark_old_messages_as_noticed(
3216 context: &Context,
3217 mut msgs: Vec<ReceivedMsg>,
3218) -> Result<()> {
3219 msgs.retain(|m| m.state.is_outgoing());
3220 if msgs.is_empty() {
3221 return Ok(());
3222 }
3223
3224 let mut msgs_by_chat: HashMap<ChatId, ReceivedMsg> = HashMap::new();
3225 for msg in msgs {
3226 let chat_id = msg.chat_id;
3227 if let Some(existing_msg) = msgs_by_chat.get(&chat_id) {
3228 if msg.sort_timestamp > existing_msg.sort_timestamp {
3229 msgs_by_chat.insert(chat_id, msg);
3230 }
3231 } else {
3232 msgs_by_chat.insert(chat_id, msg);
3233 }
3234 }
3235
3236 let changed_chats = context
3237 .sql
3238 .transaction(|transaction| {
3239 let mut changed_chats = Vec::new();
3240 for (_, msg) in msgs_by_chat {
3241 let changed_rows = transaction.execute(
3242 "UPDATE msgs
3243 SET state=?
3244 WHERE state=?
3245 AND hidden=0
3246 AND chat_id=?
3247 AND timestamp<=?;",
3248 (
3249 MessageState::InNoticed,
3250 MessageState::InFresh,
3251 msg.chat_id,
3252 msg.sort_timestamp,
3253 ),
3254 )?;
3255 if changed_rows > 0 {
3256 changed_chats.push(msg.chat_id);
3257 }
3258 }
3259 Ok(changed_chats)
3260 })
3261 .await?;
3262
3263 if !changed_chats.is_empty() {
3264 info!(
3265 context,
3266 "Marking chats as noticed because there are newer outgoing messages: {changed_chats:?}."
3267 );
3268 context.on_archived_chats_maybe_noticed();
3269 }
3270
3271 for c in changed_chats {
3272 start_chat_ephemeral_timers(context, c).await?;
3273 context.emit_event(EventType::MsgsNoticed(c));
3274 chatlist_events::emit_chatlist_item_changed(context, c);
3275 }
3276
3277 Ok(())
3278}
3279
3280pub async fn get_chat_media(
3287 context: &Context,
3288 chat_id: Option<ChatId>,
3289 msg_type: Viewtype,
3290 msg_type2: Viewtype,
3291 msg_type3: Viewtype,
3292) -> Result<Vec<MsgId>> {
3293 let list = if msg_type == Viewtype::Webxdc
3294 && msg_type2 == Viewtype::Unknown
3295 && msg_type3 == Viewtype::Unknown
3296 {
3297 context
3298 .sql
3299 .query_map_vec(
3300 "SELECT id
3301 FROM msgs
3302 WHERE (1=? OR chat_id=?)
3303 AND chat_id != ?
3304 AND type = ?
3305 AND hidden=0
3306 ORDER BY max(timestamp, timestamp_rcvd), id;",
3307 (
3308 chat_id.is_none(),
3309 chat_id.unwrap_or_else(|| ChatId::new(0)),
3310 DC_CHAT_ID_TRASH,
3311 Viewtype::Webxdc,
3312 ),
3313 |row| {
3314 let msg_id: MsgId = row.get(0)?;
3315 Ok(msg_id)
3316 },
3317 )
3318 .await?
3319 } else {
3320 context
3321 .sql
3322 .query_map_vec(
3323 "SELECT id
3324 FROM msgs
3325 WHERE (1=? OR chat_id=?)
3326 AND chat_id != ?
3327 AND type IN (?, ?, ?)
3328 AND hidden=0
3329 ORDER BY timestamp, id;",
3330 (
3331 chat_id.is_none(),
3332 chat_id.unwrap_or_else(|| ChatId::new(0)),
3333 DC_CHAT_ID_TRASH,
3334 msg_type,
3335 if msg_type2 != Viewtype::Unknown {
3336 msg_type2
3337 } else {
3338 msg_type
3339 },
3340 if msg_type3 != Viewtype::Unknown {
3341 msg_type3
3342 } else {
3343 msg_type
3344 },
3345 ),
3346 |row| {
3347 let msg_id: MsgId = row.get(0)?;
3348 Ok(msg_id)
3349 },
3350 )
3351 .await?
3352 };
3353 Ok(list)
3354}
3355
3356pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3358 context
3361 .sql
3362 .query_map_vec(
3363 "SELECT cc.contact_id
3364 FROM chats_contacts cc
3365 LEFT JOIN contacts c
3366 ON c.id=cc.contact_id
3367 WHERE cc.chat_id=? AND cc.add_timestamp >= cc.remove_timestamp
3368 ORDER BY c.id=1, c.last_seen DESC, c.id DESC;",
3369 (chat_id,),
3370 |row| {
3371 let contact_id: ContactId = row.get(0)?;
3372 Ok(contact_id)
3373 },
3374 )
3375 .await
3376}
3377
3378pub async fn get_past_chat_contacts(context: &Context, chat_id: ChatId) -> Result<Vec<ContactId>> {
3382 let now = time();
3383 context
3384 .sql
3385 .query_map_vec(
3386 "SELECT cc.contact_id
3387 FROM chats_contacts cc
3388 LEFT JOIN contacts c
3389 ON c.id=cc.contact_id
3390 WHERE cc.chat_id=?
3391 AND cc.add_timestamp < cc.remove_timestamp
3392 AND ? < cc.remove_timestamp
3393 ORDER BY c.id=1, cc.remove_timestamp DESC, c.id DESC",
3394 (chat_id, now.saturating_sub(60 * 24 * 3600)),
3395 |row| {
3396 let contact_id: ContactId = row.get(0)?;
3397 Ok(contact_id)
3398 },
3399 )
3400 .await
3401}
3402
3403pub async fn create_group(context: &Context, name: &str) -> Result<ChatId> {
3405 create_group_ex(context, Sync, create_id(), name).await
3406}
3407
3408pub async fn create_group_unencrypted(context: &Context, name: &str) -> Result<ChatId> {
3410 create_group_ex(context, Sync, String::new(), name).await
3411}
3412
3413pub(crate) async fn create_group_ex(
3420 context: &Context,
3421 sync: sync::Sync,
3422 grpid: String,
3423 name: &str,
3424) -> Result<ChatId> {
3425 let mut chat_name = sanitize_single_line(name);
3426 if chat_name.is_empty() {
3427 error!(context, "Invalid chat name: {name}.");
3430 chat_name = "…".to_string();
3431 }
3432
3433 let timestamp = create_smeared_timestamp(context);
3434 let row_id = context
3435 .sql
3436 .insert(
3437 "INSERT INTO chats
3438 (type, name, name_normalized, grpid, param, created_timestamp)
3439 VALUES(?, ?, ?, ?, \'U=1\', ?)",
3440 (
3441 Chattype::Group,
3442 &chat_name,
3443 normalize_text(&chat_name),
3444 &grpid,
3445 timestamp,
3446 ),
3447 )
3448 .await?;
3449
3450 let chat_id = ChatId::new(u32::try_from(row_id)?);
3451 add_to_chat_contacts_table(context, timestamp, chat_id, &[ContactId::SELF]).await?;
3452
3453 context.emit_msgs_changed_without_ids();
3454 chatlist_events::emit_chatlist_changed(context);
3455 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3456
3457 if !grpid.is_empty() {
3458 chat_id.add_e2ee_notice(context, timestamp).await?;
3460 }
3461
3462 if !context.get_config_bool(Config::Bot).await?
3463 && !context.get_config_bool(Config::SkipStartMessages).await?
3464 {
3465 let text = if !grpid.is_empty() {
3466 stock_str::new_group_send_first_message(context).await
3468 } else {
3469 stock_str::chat_unencrypted_explanation(context).await
3471 };
3472 add_info_msg(context, chat_id, &text).await?;
3473 }
3474 if let (true, true) = (sync.into(), !grpid.is_empty()) {
3475 let id = SyncId::Grpid(grpid);
3476 let action = SyncAction::CreateGroupEncrypted(chat_name);
3477 self::sync(context, id, action).await.log_err(context).ok();
3478 }
3479 Ok(chat_id)
3480}
3481
3482pub async fn create_broadcast(context: &Context, chat_name: String) -> Result<ChatId> {
3498 let grpid = create_id();
3499 let secret = create_broadcast_secret();
3500 create_out_broadcast_ex(context, Sync, grpid, chat_name, secret).await
3501}
3502
3503const SQL_INSERT_BROADCAST_SECRET: &str =
3504 "INSERT INTO broadcast_secrets (chat_id, secret) VALUES (?, ?)
3505 ON CONFLICT(chat_id) DO UPDATE SET secret=excluded.secret";
3506
3507pub(crate) async fn create_out_broadcast_ex(
3508 context: &Context,
3509 sync: sync::Sync,
3510 grpid: String,
3511 chat_name: String,
3512 secret: String,
3513) -> Result<ChatId> {
3514 let chat_name = sanitize_single_line(&chat_name);
3515 if chat_name.is_empty() {
3516 bail!("Invalid broadcast channel name: {chat_name}.");
3517 }
3518
3519 let timestamp = create_smeared_timestamp(context);
3520 let trans_fn = |t: &mut rusqlite::Transaction| -> Result<ChatId> {
3521 let cnt: u32 = t.query_row(
3522 "SELECT COUNT(*) FROM chats WHERE grpid=?",
3523 (&grpid,),
3524 |row| row.get(0),
3525 )?;
3526 ensure!(cnt == 0, "{cnt} chats exist with grpid {grpid}");
3527
3528 t.execute(
3529 "INSERT INTO chats
3530 (type, name, name_normalized, grpid, created_timestamp)
3531 VALUES(?, ?, ?, ?, ?)",
3532 (
3533 Chattype::OutBroadcast,
3534 &chat_name,
3535 normalize_text(&chat_name),
3536 &grpid,
3537 timestamp,
3538 ),
3539 )?;
3540 let chat_id = ChatId::new(t.last_insert_rowid().try_into()?);
3541
3542 t.execute(SQL_INSERT_BROADCAST_SECRET, (chat_id, &secret))?;
3543 Ok(chat_id)
3544 };
3545 let chat_id = context.sql.transaction(trans_fn).await?;
3546 chat_id.add_e2ee_notice(context, timestamp).await?;
3547
3548 context.emit_msgs_changed_without_ids();
3549 chatlist_events::emit_chatlist_changed(context);
3550 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3551
3552 if sync.into() {
3553 let id = SyncId::Grpid(grpid);
3554 let action = SyncAction::CreateOutBroadcast { chat_name, secret };
3555 self::sync(context, id, action).await.log_err(context).ok();
3556 }
3557
3558 Ok(chat_id)
3559}
3560
3561pub(crate) async fn load_broadcast_secret(
3562 context: &Context,
3563 chat_id: ChatId,
3564) -> Result<Option<String>> {
3565 context
3566 .sql
3567 .query_get_value(
3568 "SELECT secret FROM broadcast_secrets WHERE chat_id=?",
3569 (chat_id,),
3570 )
3571 .await
3572}
3573
3574pub(crate) async fn save_broadcast_secret(
3575 context: &Context,
3576 chat_id: ChatId,
3577 secret: &str,
3578) -> Result<()> {
3579 info!(context, "Saving broadcast secret for chat {chat_id}");
3580 context
3581 .sql
3582 .execute(SQL_INSERT_BROADCAST_SECRET, (chat_id, secret))
3583 .await?;
3584
3585 Ok(())
3586}
3587
3588pub(crate) async fn delete_broadcast_secret(context: &Context, chat_id: ChatId) -> Result<()> {
3589 info!(context, "Removing broadcast secret for chat {chat_id}");
3590 context
3591 .sql
3592 .execute("DELETE FROM broadcast_secrets WHERE chat_id=?", (chat_id,))
3593 .await?;
3594
3595 Ok(())
3596}
3597
3598pub(crate) async fn update_chat_contacts_table(
3600 context: &Context,
3601 timestamp: i64,
3602 id: ChatId,
3603 contacts: &HashSet<ContactId>,
3604) -> Result<()> {
3605 context
3606 .sql
3607 .transaction(move |transaction| {
3608 transaction.execute(
3612 "UPDATE chats_contacts
3613 SET remove_timestamp=MAX(add_timestamp+1, ?)
3614 WHERE chat_id=?",
3615 (timestamp, id),
3616 )?;
3617
3618 if !contacts.is_empty() {
3619 let mut statement = transaction.prepare(
3620 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp)
3621 VALUES (?1, ?2, ?3)
3622 ON CONFLICT (chat_id, contact_id)
3623 DO UPDATE SET add_timestamp=remove_timestamp",
3624 )?;
3625
3626 for contact_id in contacts {
3627 statement.execute((id, contact_id, timestamp))?;
3631 }
3632 }
3633 Ok(())
3634 })
3635 .await?;
3636 Ok(())
3637}
3638
3639pub(crate) async fn add_to_chat_contacts_table(
3641 context: &Context,
3642 timestamp: i64,
3643 chat_id: ChatId,
3644 contact_ids: &[ContactId],
3645) -> Result<()> {
3646 context
3647 .sql
3648 .transaction(move |transaction| {
3649 let mut add_statement = transaction.prepare(
3650 "INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp) VALUES(?1, ?2, ?3)
3651 ON CONFLICT (chat_id, contact_id)
3652 DO UPDATE SET add_timestamp=MAX(remove_timestamp, ?3)",
3653 )?;
3654
3655 for contact_id in contact_ids {
3656 add_statement.execute((chat_id, contact_id, timestamp))?;
3657 }
3658 Ok(())
3659 })
3660 .await?;
3661
3662 Ok(())
3663}
3664
3665pub(crate) async fn remove_from_chat_contacts_table(
3668 context: &Context,
3669 chat_id: ChatId,
3670 contact_id: ContactId,
3671) -> Result<()> {
3672 let now = time();
3673 context
3674 .sql
3675 .execute(
3676 "UPDATE chats_contacts
3677 SET remove_timestamp=MAX(add_timestamp+1, ?)
3678 WHERE chat_id=? AND contact_id=?",
3679 (now, chat_id, contact_id),
3680 )
3681 .await?;
3682 Ok(())
3683}
3684
3685pub(crate) async fn remove_from_chat_contacts_table_without_trace(
3693 context: &Context,
3694 chat_id: ChatId,
3695 contact_id: ContactId,
3696) -> Result<()> {
3697 context
3698 .sql
3699 .execute(
3700 "DELETE FROM chats_contacts
3701 WHERE chat_id=? AND contact_id=?",
3702 (chat_id, contact_id),
3703 )
3704 .await?;
3705
3706 Ok(())
3707}
3708
3709pub async fn add_contact_to_chat(
3712 context: &Context,
3713 chat_id: ChatId,
3714 contact_id: ContactId,
3715) -> Result<()> {
3716 add_contact_to_chat_ex(context, Sync, chat_id, contact_id, false).await?;
3717 Ok(())
3718}
3719
3720pub(crate) async fn add_contact_to_chat_ex(
3721 context: &Context,
3722 mut sync: sync::Sync,
3723 chat_id: ChatId,
3724 contact_id: ContactId,
3725 from_handshake: bool,
3726) -> Result<bool> {
3727 ensure!(!chat_id.is_special(), "can not add member to special chats");
3728 let contact = Contact::get_by_id(context, contact_id).await?;
3729 let mut msg = Message::new(Viewtype::default());
3730
3731 chat_id.reset_gossiped_timestamp(context).await?;
3732
3733 let mut chat = Chat::load_from_db(context, chat_id).await?;
3735 ensure!(
3736 chat.typ == Chattype::Group || (from_handshake && chat.typ == Chattype::OutBroadcast),
3737 "{chat_id} is not a group where one can add members",
3738 );
3739 ensure!(
3740 Contact::real_exists_by_id(context, contact_id).await? || contact_id == ContactId::SELF,
3741 "invalid contact_id {contact_id} for adding to group"
3742 );
3743 ensure!(
3744 chat.typ != Chattype::OutBroadcast || contact_id != ContactId::SELF,
3745 "Cannot add SELF to broadcast channel."
3746 );
3747 match chat.is_encrypted(context).await? {
3748 true => ensure!(
3749 contact.is_key_contact(),
3750 "Only key-contacts can be added to encrypted chats"
3751 ),
3752 false => ensure!(
3753 !contact.is_key_contact(),
3754 "Only address-contacts can be added to unencrypted chats"
3755 ),
3756 }
3757
3758 if !chat.is_self_in_chat(context).await? {
3759 context.emit_event(EventType::ErrorSelfNotInGroup(
3760 "Cannot add contact to group; self not in group.".into(),
3761 ));
3762 warn!(
3763 context,
3764 "Can not add contact because the account is not part of the group/broadcast."
3765 );
3766 return Ok(false);
3767 }
3768
3769 let sync_qr_code_tokens;
3770 if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 {
3771 chat.param
3772 .remove(Param::Unpromoted)
3773 .set_i64(Param::GroupNameTimestamp, smeared_time(context));
3774 chat.update_param(context).await?;
3775 sync_qr_code_tokens = true;
3776 } else {
3777 sync_qr_code_tokens = false;
3778 }
3779
3780 if context.is_self_addr(contact.get_addr()).await? {
3781 warn!(
3784 context,
3785 "Invalid attempt to add self e-mail address to group."
3786 );
3787 return Ok(false);
3788 }
3789
3790 if is_contact_in_chat(context, chat_id, contact_id).await? {
3791 if !from_handshake {
3792 return Ok(true);
3793 }
3794 } else {
3795 add_to_chat_contacts_table(context, time(), chat_id, &[contact_id]).await?;
3797 }
3798 if chat.is_promoted() {
3799 msg.viewtype = Viewtype::Text;
3800
3801 let contact_addr = contact.get_addr().to_lowercase();
3802 let added_by = if from_handshake && chat.typ == Chattype::OutBroadcast {
3803 ContactId::UNDEFINED
3808 } else {
3809 ContactId::SELF
3810 };
3811 msg.text = stock_str::msg_add_member_local(context, contact.id, added_by).await;
3812 msg.param.set_cmd(SystemMessage::MemberAddedToGroup);
3813 msg.param.set(Param::Arg, contact_addr);
3814 msg.param.set_int(Param::Arg2, from_handshake.into());
3815 let fingerprint = contact.fingerprint().map(|f| f.hex());
3816 msg.param.set_optional(Param::Arg4, fingerprint);
3817 msg.param
3818 .set_int(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
3819 if chat.typ == Chattype::OutBroadcast {
3820 let secret = load_broadcast_secret(context, chat_id)
3821 .await?
3822 .context("Failed to find broadcast shared secret")?;
3823 msg.param.set(PARAM_BROADCAST_SECRET, secret);
3824 }
3825 send_msg(context, chat_id, &mut msg).await?;
3826
3827 sync = Nosync;
3828 if sync_qr_code_tokens
3834 && context
3835 .sync_qr_code_tokens(Some(chat.grpid.as_str()))
3836 .await
3837 .log_err(context)
3838 .is_ok()
3839 {
3840 context.scheduler.interrupt_inbox().await;
3841 }
3842 }
3843 context.emit_event(EventType::ChatModified(chat_id));
3844 if sync.into() {
3845 chat.sync_contacts(context).await.log_err(context).ok();
3846 }
3847 Ok(true)
3848}
3849
3850pub(crate) async fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result<bool> {
3856 let timestamp_some_days_ago = time() - DC_RESEND_USER_AVATAR_DAYS * 24 * 60 * 60;
3857 let needs_attach = context
3858 .sql
3859 .query_map(
3860 "SELECT c.selfavatar_sent
3861 FROM chats_contacts cc
3862 LEFT JOIN contacts c ON c.id=cc.contact_id
3863 WHERE cc.chat_id=? AND cc.contact_id!=? AND cc.add_timestamp >= cc.remove_timestamp",
3864 (chat_id, ContactId::SELF),
3865 |row| {
3866 let selfavatar_sent: i64 = row.get(0)?;
3867 Ok(selfavatar_sent)
3868 },
3869 |rows| {
3870 let mut needs_attach = false;
3871 for row in rows {
3872 let selfavatar_sent = row?;
3873 if selfavatar_sent < timestamp_some_days_ago {
3874 needs_attach = true;
3875 }
3876 }
3877 Ok(needs_attach)
3878 },
3879 )
3880 .await?;
3881 Ok(needs_attach)
3882}
3883
3884#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
3886pub enum MuteDuration {
3887 NotMuted,
3889
3890 Forever,
3892
3893 Until(std::time::SystemTime),
3895}
3896
3897impl rusqlite::types::ToSql for MuteDuration {
3898 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
3899 let duration: i64 = match &self {
3900 MuteDuration::NotMuted => 0,
3901 MuteDuration::Forever => -1,
3902 MuteDuration::Until(when) => {
3903 let duration = when
3904 .duration_since(SystemTime::UNIX_EPOCH)
3905 .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
3906 i64::try_from(duration.as_secs())
3907 .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?
3908 }
3909 };
3910 let val = rusqlite::types::Value::Integer(duration);
3911 let out = rusqlite::types::ToSqlOutput::Owned(val);
3912 Ok(out)
3913 }
3914}
3915
3916impl rusqlite::types::FromSql for MuteDuration {
3917 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
3918 match i64::column_result(value)? {
3921 0 => Ok(MuteDuration::NotMuted),
3922 -1 => Ok(MuteDuration::Forever),
3923 n if n > 0 => match SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(n as u64)) {
3924 Some(t) => Ok(MuteDuration::Until(t)),
3925 None => Err(rusqlite::types::FromSqlError::OutOfRange(n)),
3926 },
3927 _ => Ok(MuteDuration::NotMuted),
3928 }
3929 }
3930}
3931
3932pub async fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<()> {
3934 set_muted_ex(context, Sync, chat_id, duration).await
3935}
3936
3937pub(crate) async fn set_muted_ex(
3938 context: &Context,
3939 sync: sync::Sync,
3940 chat_id: ChatId,
3941 duration: MuteDuration,
3942) -> Result<()> {
3943 ensure!(!chat_id.is_special(), "Invalid chat ID");
3944 context
3945 .sql
3946 .execute(
3947 "UPDATE chats SET muted_until=? WHERE id=?;",
3948 (duration, chat_id),
3949 )
3950 .await
3951 .context(format!("Failed to set mute duration for {chat_id}"))?;
3952 context.emit_event(EventType::ChatModified(chat_id));
3953 chatlist_events::emit_chatlist_item_changed(context, chat_id);
3954 if sync.into() {
3955 let chat = Chat::load_from_db(context, chat_id).await?;
3956 chat.sync(context, SyncAction::SetMuted(duration))
3957 .await
3958 .log_err(context)
3959 .ok();
3960 }
3961 Ok(())
3962}
3963
3964pub async fn remove_contact_from_chat(
3966 context: &Context,
3967 chat_id: ChatId,
3968 contact_id: ContactId,
3969) -> Result<()> {
3970 ensure!(
3971 !chat_id.is_special(),
3972 "bad chat_id, can not be special chat: {chat_id}"
3973 );
3974 ensure!(
3975 !contact_id.is_special() || contact_id == ContactId::SELF,
3976 "Cannot remove special contact"
3977 );
3978
3979 let chat = Chat::load_from_db(context, chat_id).await?;
3980 if chat.typ == Chattype::InBroadcast {
3981 ensure!(
3982 contact_id == ContactId::SELF,
3983 "Cannot remove other member from incoming broadcast channel"
3984 );
3985 delete_broadcast_secret(context, chat_id).await?;
3986 }
3987
3988 if matches!(
3989 chat.typ,
3990 Chattype::Group | Chattype::OutBroadcast | Chattype::InBroadcast
3991 ) {
3992 if !chat.is_self_in_chat(context).await? {
3993 let err_msg = format!(
3994 "Cannot remove contact {contact_id} from chat {chat_id}: self not in group."
3995 );
3996 context.emit_event(EventType::ErrorSelfNotInGroup(err_msg.clone()));
3997 bail!("{err_msg}");
3998 } else {
3999 let mut sync = Nosync;
4000
4001 if chat.is_promoted() {
4002 remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
4003 } else {
4004 remove_from_chat_contacts_table_without_trace(context, chat_id, contact_id).await?;
4005 }
4006
4007 if let Some(contact) = Contact::get_by_id_optional(context, contact_id).await? {
4011 if chat.is_promoted() {
4012 let addr = contact.get_addr();
4013 let fingerprint = contact.fingerprint().map(|f| f.hex());
4014
4015 let res = send_member_removal_msg(
4016 context,
4017 &chat,
4018 contact_id,
4019 addr,
4020 fingerprint.as_deref(),
4021 )
4022 .await;
4023
4024 if contact_id == ContactId::SELF {
4025 res?;
4026 } else if let Err(e) = res {
4027 warn!(
4028 context,
4029 "remove_contact_from_chat({chat_id}, {contact_id}): send_msg() failed: {e:#}."
4030 );
4031 }
4032 } else {
4033 sync = Sync;
4034 }
4035 }
4036 context.emit_event(EventType::ChatModified(chat_id));
4037 if sync.into() {
4038 chat.sync_contacts(context).await.log_err(context).ok();
4039 }
4040 }
4041 } else {
4042 bail!("Cannot remove members from non-group chats.");
4043 }
4044
4045 Ok(())
4046}
4047
4048async fn send_member_removal_msg(
4049 context: &Context,
4050 chat: &Chat,
4051 contact_id: ContactId,
4052 addr: &str,
4053 fingerprint: Option<&str>,
4054) -> Result<MsgId> {
4055 let mut msg = Message::new(Viewtype::Text);
4056
4057 if contact_id == ContactId::SELF {
4058 if chat.typ == Chattype::InBroadcast {
4059 msg.text = stock_str::msg_you_left_broadcast(context).await;
4060 } else {
4061 msg.text = stock_str::msg_group_left_local(context, ContactId::SELF).await;
4062 }
4063 } else {
4064 msg.text = stock_str::msg_del_member_local(context, contact_id, ContactId::SELF).await;
4065 }
4066
4067 msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup);
4068 msg.param.set(Param::Arg, addr.to_lowercase());
4069 msg.param.set_optional(Param::Arg4, fingerprint);
4070 msg.param
4071 .set(Param::ContactAddedRemoved, contact_id.to_u32());
4072
4073 send_msg(context, chat.id, &mut msg).await
4074}
4075
4076pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -> Result<()> {
4078 rename_ex(context, Sync, chat_id, new_name).await
4079}
4080
4081async fn rename_ex(
4082 context: &Context,
4083 mut sync: sync::Sync,
4084 chat_id: ChatId,
4085 new_name: &str,
4086) -> Result<()> {
4087 let new_name = sanitize_single_line(new_name);
4088 let mut success = false;
4090
4091 ensure!(!new_name.is_empty(), "Invalid name");
4092 ensure!(!chat_id.is_special(), "Invalid chat ID");
4093
4094 let chat = Chat::load_from_db(context, chat_id).await?;
4095 let mut msg = Message::new(Viewtype::default());
4096
4097 if chat.typ == Chattype::Group
4098 || chat.typ == Chattype::Mailinglist
4099 || chat.typ == Chattype::OutBroadcast
4100 {
4101 if chat.name == new_name {
4102 success = true;
4103 } else if !chat.is_self_in_chat(context).await? {
4104 context.emit_event(EventType::ErrorSelfNotInGroup(
4105 "Cannot set chat name; self not in group".into(),
4106 ));
4107 } else {
4108 context
4109 .sql
4110 .execute(
4111 "UPDATE chats SET name=?, name_normalized=? WHERE id=?",
4112 (&new_name, normalize_text(&new_name), chat_id),
4113 )
4114 .await?;
4115 if chat.is_promoted()
4116 && !chat.is_mailing_list()
4117 && sanitize_single_line(&chat.name) != new_name
4118 {
4119 msg.viewtype = Viewtype::Text;
4120 msg.text =
4121 stock_str::msg_grp_name(context, &chat.name, &new_name, ContactId::SELF).await;
4122 msg.param.set_cmd(SystemMessage::GroupNameChanged);
4123 if !chat.name.is_empty() {
4124 msg.param.set(Param::Arg, &chat.name);
4125 }
4126 msg.id = send_msg(context, chat_id, &mut msg).await?;
4127 context.emit_msgs_changed(chat_id, msg.id);
4128 sync = Nosync;
4129 }
4130 context.emit_event(EventType::ChatModified(chat_id));
4131 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4132 success = true;
4133 }
4134 }
4135
4136 if !success {
4137 bail!("Failed to set name");
4138 }
4139 if sync.into() && chat.name != new_name {
4140 let sync_name = new_name.to_string();
4141 chat.sync(context, SyncAction::Rename(sync_name))
4142 .await
4143 .log_err(context)
4144 .ok();
4145 }
4146 Ok(())
4147}
4148
4149pub async fn set_chat_profile_image(
4155 context: &Context,
4156 chat_id: ChatId,
4157 new_image: &str, ) -> Result<()> {
4159 ensure!(!chat_id.is_special(), "Invalid chat ID");
4160 let mut chat = Chat::load_from_db(context, chat_id).await?;
4161 ensure!(
4162 chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast,
4163 "Can only set profile image for groups / broadcasts"
4164 );
4165 ensure!(
4166 !chat.grpid.is_empty(),
4167 "Cannot set profile image for ad hoc groups"
4168 );
4169 if !chat.is_self_in_chat(context).await? {
4171 context.emit_event(EventType::ErrorSelfNotInGroup(
4172 "Cannot set chat profile image; self not in group.".into(),
4173 ));
4174 bail!("Failed to set profile image");
4175 }
4176 let mut msg = Message::new(Viewtype::Text);
4177 msg.param
4178 .set_int(Param::Cmd, SystemMessage::GroupImageChanged as i32);
4179 if new_image.is_empty() {
4180 chat.param.remove(Param::ProfileImage);
4181 msg.param.remove(Param::Arg);
4182 msg.text = stock_str::msg_grp_img_deleted(context, ContactId::SELF).await;
4183 } else {
4184 let mut image_blob = BlobObject::create_and_deduplicate(
4185 context,
4186 Path::new(new_image),
4187 Path::new(new_image),
4188 )?;
4189 image_blob.recode_to_avatar_size(context).await?;
4190 chat.param.set(Param::ProfileImage, image_blob.as_name());
4191 msg.param.set(Param::Arg, image_blob.as_name());
4192 msg.text = stock_str::msg_grp_img_changed(context, ContactId::SELF).await;
4193 }
4194 chat.update_param(context).await?;
4195 if chat.is_promoted() {
4196 msg.id = send_msg(context, chat_id, &mut msg).await?;
4197 context.emit_msgs_changed(chat_id, msg.id);
4198 }
4199 context.emit_event(EventType::ChatModified(chat_id));
4200 chatlist_events::emit_chatlist_item_changed(context, chat_id);
4201 Ok(())
4202}
4203
4204pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Result<()> {
4206 ensure!(!msg_ids.is_empty(), "empty msgs_ids: nothing to forward");
4207 ensure!(!chat_id.is_special(), "can not forward to special chat");
4208
4209 let mut created_msgs: Vec<MsgId> = Vec::new();
4210 let mut curr_timestamp: i64;
4211
4212 chat_id
4213 .unarchive_if_not_muted(context, MessageState::Undefined)
4214 .await?;
4215 let mut chat = Chat::load_from_db(context, chat_id).await?;
4216 if let Some(reason) = chat.why_cant_send(context).await? {
4217 bail!("cannot send to {chat_id}: {reason}");
4218 }
4219 curr_timestamp = create_smeared_timestamps(context, msg_ids.len());
4220 let mut msgs = Vec::with_capacity(msg_ids.len());
4221 for id in msg_ids {
4222 let ts: i64 = context
4223 .sql
4224 .query_get_value("SELECT timestamp FROM msgs WHERE id=?", (id,))
4225 .await?
4226 .with_context(|| format!("No message {id}"))?;
4227 msgs.push((ts, *id));
4228 }
4229 msgs.sort_unstable();
4230 for (_, id) in msgs {
4231 let src_msg_id: MsgId = id;
4232 let mut msg = Message::load_from_db(context, src_msg_id).await?;
4233 if msg.state == MessageState::OutDraft {
4234 bail!("cannot forward drafts.");
4235 }
4236
4237 if msg.get_viewtype() != Viewtype::Sticker {
4238 msg.param
4239 .set_int(Param::Forwarded, src_msg_id.to_u32() as i32);
4240 }
4241
4242 if msg.get_viewtype() == Viewtype::Call {
4243 msg.viewtype = Viewtype::Text;
4244 }
4245
4246 msg.param.remove(Param::GuaranteeE2ee);
4247 msg.param.remove(Param::ForcePlaintext);
4248 msg.param.remove(Param::Cmd);
4249 msg.param.remove(Param::OverrideSenderDisplayname);
4250 msg.param.remove(Param::WebxdcDocument);
4251 msg.param.remove(Param::WebxdcDocumentTimestamp);
4252 msg.param.remove(Param::WebxdcSummary);
4253 msg.param.remove(Param::WebxdcSummaryTimestamp);
4254 msg.param.remove(Param::IsEdited);
4255 msg.param.remove(Param::WebrtcRoom);
4256 msg.param.remove(Param::WebrtcAccepted);
4257 msg.in_reply_to = None;
4258
4259 msg.subject = "".to_string();
4261
4262 msg.state = MessageState::OutPending;
4263 msg.rfc724_mid = create_outgoing_rfc724_mid();
4264 msg.timestamp_sort = curr_timestamp;
4265 chat.prepare_msg_raw(context, &mut msg, None).await?;
4266
4267 curr_timestamp += 1;
4268 if !create_send_msg_jobs(context, &mut msg).await?.is_empty() {
4269 context.scheduler.interrupt_smtp().await;
4270 }
4271 created_msgs.push(msg.id);
4272 }
4273 for msg_id in created_msgs {
4274 context.emit_msgs_changed(chat_id, msg_id);
4275 }
4276 Ok(())
4277}
4278
4279pub async fn save_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
4282 let mut msgs = Vec::with_capacity(msg_ids.len());
4283 for id in msg_ids {
4284 let ts: i64 = context
4285 .sql
4286 .query_get_value("SELECT timestamp FROM msgs WHERE id=?", (id,))
4287 .await?
4288 .with_context(|| format!("No message {id}"))?;
4289 msgs.push((ts, *id));
4290 }
4291 msgs.sort_unstable();
4292 for (_, src_msg_id) in msgs {
4293 let dest_rfc724_mid = create_outgoing_rfc724_mid();
4294 let src_rfc724_mid = save_copy_in_self_talk(context, src_msg_id, &dest_rfc724_mid).await?;
4295 context
4296 .add_sync_item(SyncData::SaveMessage {
4297 src: src_rfc724_mid,
4298 dest: dest_rfc724_mid,
4299 })
4300 .await?;
4301 }
4302 context.scheduler.interrupt_inbox().await;
4303 Ok(())
4304}
4305
4306pub(crate) async fn save_copy_in_self_talk(
4312 context: &Context,
4313 src_msg_id: MsgId,
4314 dest_rfc724_mid: &String,
4315) -> Result<String> {
4316 let dest_chat_id = ChatId::create_for_contact(context, ContactId::SELF).await?;
4317 let mut msg = Message::load_from_db(context, src_msg_id).await?;
4318 msg.param.remove(Param::Cmd);
4319 msg.param.remove(Param::WebxdcDocument);
4320 msg.param.remove(Param::WebxdcDocumentTimestamp);
4321 msg.param.remove(Param::WebxdcSummary);
4322 msg.param.remove(Param::WebxdcSummaryTimestamp);
4323
4324 if !msg.original_msg_id.is_unset() {
4325 bail!("message already saved.");
4326 }
4327
4328 let copy_fields = "from_id, to_id, timestamp_rcvd, type, txt,
4329 mime_modified, mime_headers, mime_compressed, mime_in_reply_to, subject, msgrmsg";
4330 let row_id = context
4331 .sql
4332 .insert(
4333 &format!(
4334 "INSERT INTO msgs ({copy_fields},
4335 timestamp_sent,
4336 chat_id, rfc724_mid, state, timestamp, param, starred)
4337 SELECT {copy_fields},
4338 -- Outgoing messages on originating device
4339 -- have timestamp_sent == 0.
4340 -- We copy sort timestamp instead
4341 -- so UIs display the same timestamp
4342 -- for saved and original message.
4343 IIF(timestamp_sent == 0, timestamp, timestamp_sent),
4344 ?, ?, ?, ?, ?, ?
4345 FROM msgs WHERE id=?;"
4346 ),
4347 (
4348 dest_chat_id,
4349 dest_rfc724_mid,
4350 if msg.from_id == ContactId::SELF {
4351 MessageState::OutDelivered
4352 } else {
4353 MessageState::InSeen
4354 },
4355 create_smeared_timestamp(context),
4356 msg.param.to_string(),
4357 src_msg_id,
4358 src_msg_id,
4359 ),
4360 )
4361 .await?;
4362 let dest_msg_id = MsgId::new(row_id.try_into()?);
4363
4364 context.emit_msgs_changed(msg.chat_id, src_msg_id);
4365 context.emit_msgs_changed(dest_chat_id, dest_msg_id);
4366 chatlist_events::emit_chatlist_changed(context);
4367 chatlist_events::emit_chatlist_item_changed(context, dest_chat_id);
4368
4369 Ok(msg.rfc724_mid)
4370}
4371
4372pub async fn resend_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
4376 let mut msgs: Vec<Message> = Vec::new();
4377 for msg_id in msg_ids {
4378 let msg = Message::load_from_db(context, *msg_id).await?;
4379 ensure!(
4380 msg.from_id == ContactId::SELF,
4381 "can resend only own messages"
4382 );
4383 ensure!(!msg.is_info(), "cannot resend info messages");
4384 msgs.push(msg)
4385 }
4386
4387 for mut msg in msgs {
4388 match msg.get_state() {
4389 MessageState::OutPending
4391 | MessageState::OutFailed
4392 | MessageState::OutDelivered
4393 | MessageState::OutMdnRcvd => {
4394 message::update_msg_state(context, msg.id, MessageState::OutPending).await?
4395 }
4396 msg_state => bail!("Unexpected message state {msg_state}"),
4397 }
4398 msg.timestamp_sort = create_smeared_timestamp(context);
4399 if create_send_msg_jobs(context, &mut msg).await?.is_empty() {
4400 continue;
4401 }
4402
4403 context.emit_event(EventType::MsgsChanged {
4407 chat_id: msg.chat_id,
4408 msg_id: msg.id,
4409 });
4410 chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
4412
4413 if msg.viewtype == Viewtype::Webxdc {
4414 let conn_fn = |conn: &mut rusqlite::Connection| {
4415 let range = conn.query_row(
4416 "SELECT IFNULL(min(id), 1), IFNULL(max(id), 0) \
4417 FROM msgs_status_updates WHERE msg_id=?",
4418 (msg.id,),
4419 |row| {
4420 let min_id: StatusUpdateSerial = row.get(0)?;
4421 let max_id: StatusUpdateSerial = row.get(1)?;
4422 Ok((min_id, max_id))
4423 },
4424 )?;
4425 if range.0 > range.1 {
4426 return Ok(());
4427 };
4428 conn.execute(
4432 "INSERT INTO smtp_status_updates (msg_id, first_serial, last_serial, descr) \
4433 VALUES(?, ?, ?, '') \
4434 ON CONFLICT(msg_id) \
4435 DO UPDATE SET first_serial=min(first_serial - 1, excluded.first_serial)",
4436 (msg.id, range.0, range.1),
4437 )?;
4438 Ok(())
4439 };
4440 context.sql.call_write(conn_fn).await?;
4441 }
4442 context.scheduler.interrupt_smtp().await;
4443 }
4444 Ok(())
4445}
4446
4447pub(crate) async fn get_chat_cnt(context: &Context) -> Result<usize> {
4448 if context.sql.is_open().await {
4449 let count = context
4451 .sql
4452 .count("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;", ())
4453 .await?;
4454 Ok(count)
4455 } else {
4456 Ok(0)
4457 }
4458}
4459
4460pub(crate) async fn get_chat_id_by_grpid(
4462 context: &Context,
4463 grpid: &str,
4464) -> Result<Option<(ChatId, Blocked)>> {
4465 context
4466 .sql
4467 .query_row_optional(
4468 "SELECT id, blocked FROM chats WHERE grpid=?;",
4469 (grpid,),
4470 |row| {
4471 let chat_id = row.get::<_, ChatId>(0)?;
4472
4473 let b = row.get::<_, Option<Blocked>>(1)?.unwrap_or_default();
4474 Ok((chat_id, b))
4475 },
4476 )
4477 .await
4478}
4479
4480pub async fn add_device_msg_with_importance(
4485 context: &Context,
4486 label: Option<&str>,
4487 msg: Option<&mut Message>,
4488 important: bool,
4489) -> Result<MsgId> {
4490 ensure!(
4491 label.is_some() || msg.is_some(),
4492 "device-messages need label, msg or both"
4493 );
4494 let mut chat_id = ChatId::new(0);
4495 let mut msg_id = MsgId::new_unset();
4496
4497 if let Some(label) = label
4498 && was_device_msg_ever_added(context, label).await?
4499 {
4500 info!(context, "Device-message {label} already added.");
4501 return Ok(msg_id);
4502 }
4503
4504 if let Some(msg) = msg {
4505 chat_id = ChatId::get_for_contact(context, ContactId::DEVICE).await?;
4506
4507 let rfc724_mid = create_outgoing_rfc724_mid();
4508 let timestamp_sent = create_smeared_timestamp(context);
4509
4510 msg.timestamp_sort = timestamp_sent;
4513 if let Some(last_msg_time) = chat_id.get_timestamp(context).await?
4514 && msg.timestamp_sort <= last_msg_time
4515 {
4516 msg.timestamp_sort = last_msg_time + 1;
4517 }
4518 prepare_msg_blob(context, msg).await?;
4519 let state = MessageState::InFresh;
4520 let row_id = context
4521 .sql
4522 .insert(
4523 "INSERT INTO msgs (
4524 chat_id,
4525 from_id,
4526 to_id,
4527 timestamp,
4528 timestamp_sent,
4529 timestamp_rcvd,
4530 type,state,
4531 txt,
4532 txt_normalized,
4533 param,
4534 rfc724_mid)
4535 VALUES (?,?,?,?,?,?,?,?,?,?,?,?);",
4536 (
4537 chat_id,
4538 ContactId::DEVICE,
4539 ContactId::SELF,
4540 msg.timestamp_sort,
4541 timestamp_sent,
4542 timestamp_sent, msg.viewtype,
4544 state,
4545 &msg.text,
4546 normalize_text(&msg.text),
4547 msg.param.to_string(),
4548 rfc724_mid,
4549 ),
4550 )
4551 .await?;
4552 context.new_msgs_notify.notify_one();
4553
4554 msg_id = MsgId::new(u32::try_from(row_id)?);
4555 if !msg.hidden {
4556 chat_id.unarchive_if_not_muted(context, state).await?;
4557 }
4558 }
4559
4560 if let Some(label) = label {
4561 context
4562 .sql
4563 .execute("INSERT INTO devmsglabels (label) VALUES (?);", (label,))
4564 .await?;
4565 }
4566
4567 if !msg_id.is_unset() {
4568 chat_id.emit_msg_event(context, msg_id, important);
4569 }
4570
4571 Ok(msg_id)
4572}
4573
4574pub async fn add_device_msg(
4576 context: &Context,
4577 label: Option<&str>,
4578 msg: Option<&mut Message>,
4579) -> Result<MsgId> {
4580 add_device_msg_with_importance(context, label, msg, false).await
4581}
4582
4583pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result<bool> {
4585 ensure!(!label.is_empty(), "empty label");
4586 let exists = context
4587 .sql
4588 .exists(
4589 "SELECT COUNT(label) FROM devmsglabels WHERE label=?",
4590 (label,),
4591 )
4592 .await?;
4593
4594 Ok(exists)
4595}
4596
4597pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Result<()> {
4605 context
4606 .sql
4607 .execute("DELETE FROM msgs WHERE from_id=?;", (ContactId::DEVICE,))
4608 .await?;
4609 context.sql.execute("DELETE FROM devmsglabels;", ()).await?;
4610
4611 context
4613 .sql
4614 .execute(
4615 r#"INSERT INTO devmsglabels (label) VALUES ("core-welcome-image"), ("core-welcome")"#,
4616 (),
4617 )
4618 .await?;
4619 context
4620 .set_config_internal(Config::QuotaExceeding, None)
4621 .await?;
4622 Ok(())
4623}
4624
4625#[expect(clippy::too_many_arguments)]
4630pub(crate) async fn add_info_msg_with_cmd(
4631 context: &Context,
4632 chat_id: ChatId,
4633 text: &str,
4634 cmd: SystemMessage,
4635 timestamp_sort: Option<i64>,
4638 timestamp_sent_rcvd: i64,
4640 parent: Option<&Message>,
4641 from_id: Option<ContactId>,
4642 added_removed_id: Option<ContactId>,
4643) -> Result<MsgId> {
4644 let rfc724_mid = create_outgoing_rfc724_mid();
4645 let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?;
4646
4647 let mut param = Params::new();
4648 if cmd != SystemMessage::Unknown {
4649 param.set_cmd(cmd);
4650 }
4651 if let Some(contact_id) = added_removed_id {
4652 param.set(Param::ContactAddedRemoved, contact_id.to_u32().to_string());
4653 }
4654
4655 let timestamp_sort = if let Some(ts) = timestamp_sort {
4656 ts
4657 } else {
4658 let sort_to_bottom = true;
4659 let (received, incoming) = (false, false);
4660 chat_id
4661 .calc_sort_timestamp(
4662 context,
4663 smeared_time(context),
4664 sort_to_bottom,
4665 received,
4666 incoming,
4667 )
4668 .await?
4669 };
4670
4671 let row_id =
4672 context.sql.insert(
4673 "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)
4674 VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?);",
4675 (
4676 chat_id,
4677 from_id.unwrap_or(ContactId::INFO),
4678 ContactId::INFO,
4679 timestamp_sort,
4680 timestamp_sent_rcvd,
4681 timestamp_sent_rcvd,
4682 Viewtype::Text,
4683 MessageState::InNoticed,
4684 text,
4685 normalize_text(text),
4686 rfc724_mid,
4687 ephemeral_timer,
4688 param.to_string(),
4689 parent.map(|msg|msg.rfc724_mid.clone()).unwrap_or_default()
4690 )
4691 ).await?;
4692 context.new_msgs_notify.notify_one();
4693
4694 let msg_id = MsgId::new(row_id.try_into()?);
4695 context.emit_msgs_changed(chat_id, msg_id);
4696
4697 Ok(msg_id)
4698}
4699
4700pub(crate) async fn add_info_msg(context: &Context, chat_id: ChatId, text: &str) -> Result<MsgId> {
4702 add_info_msg_with_cmd(
4703 context,
4704 chat_id,
4705 text,
4706 SystemMessage::Unknown,
4707 None,
4708 time(),
4709 None,
4710 None,
4711 None,
4712 )
4713 .await
4714}
4715
4716pub(crate) async fn update_msg_text_and_timestamp(
4717 context: &Context,
4718 chat_id: ChatId,
4719 msg_id: MsgId,
4720 text: &str,
4721 timestamp: i64,
4722) -> Result<()> {
4723 context
4724 .sql
4725 .execute(
4726 "UPDATE msgs SET txt=?, txt_normalized=?, timestamp=? WHERE id=?;",
4727 (text, normalize_text(text), timestamp, msg_id),
4728 )
4729 .await?;
4730 context.emit_msgs_changed(chat_id, msg_id);
4731 Ok(())
4732}
4733
4734async fn set_contacts_by_addrs(context: &Context, id: ChatId, addrs: &[String]) -> Result<()> {
4736 let chat = Chat::load_from_db(context, id).await?;
4737 ensure!(
4738 !chat.is_encrypted(context).await?,
4739 "Cannot add address-contacts to encrypted chat {id}"
4740 );
4741 ensure!(
4742 chat.typ == Chattype::OutBroadcast,
4743 "{id} is not a broadcast list",
4744 );
4745 let mut contacts = HashSet::new();
4746 for addr in addrs {
4747 let contact_addr = ContactAddress::new(addr)?;
4748 let contact = Contact::add_or_lookup(context, "", &contact_addr, Origin::Hidden)
4749 .await?
4750 .0;
4751 contacts.insert(contact);
4752 }
4753 let contacts_old = HashSet::<ContactId>::from_iter(get_chat_contacts(context, id).await?);
4754 if contacts == contacts_old {
4755 return Ok(());
4756 }
4757 context
4758 .sql
4759 .transaction(move |transaction| {
4760 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (id,))?;
4761
4762 let mut statement = transaction
4765 .prepare("INSERT INTO chats_contacts (chat_id, contact_id) VALUES (?, ?)")?;
4766 for contact_id in &contacts {
4767 statement.execute((id, contact_id))?;
4768 }
4769 Ok(())
4770 })
4771 .await?;
4772 context.emit_event(EventType::ChatModified(id));
4773 Ok(())
4774}
4775
4776async fn set_contacts_by_fingerprints(
4780 context: &Context,
4781 id: ChatId,
4782 fingerprint_addrs: &[(String, String)],
4783) -> Result<()> {
4784 let chat = Chat::load_from_db(context, id).await?;
4785 ensure!(
4786 chat.is_encrypted(context).await?,
4787 "Cannot add key-contacts to unencrypted chat {id}"
4788 );
4789 ensure!(
4790 matches!(chat.typ, Chattype::Group | Chattype::OutBroadcast),
4791 "{id} is not a group or broadcast",
4792 );
4793 let mut contacts = HashSet::new();
4794 for (fingerprint, addr) in fingerprint_addrs {
4795 let contact = Contact::add_or_lookup_ex(context, "", addr, fingerprint, Origin::Hidden)
4796 .await?
4797 .0;
4798 contacts.insert(contact);
4799 }
4800 let contacts_old = HashSet::<ContactId>::from_iter(get_chat_contacts(context, id).await?);
4801 if contacts == contacts_old {
4802 return Ok(());
4803 }
4804 context
4805 .sql
4806 .transaction(move |transaction| {
4807 transaction.execute("DELETE FROM chats_contacts WHERE chat_id=?", (id,))?;
4808
4809 let mut statement = transaction
4812 .prepare("INSERT INTO chats_contacts (chat_id, contact_id) VALUES (?, ?)")?;
4813 for contact_id in &contacts {
4814 statement.execute((id, contact_id))?;
4815 }
4816 Ok(())
4817 })
4818 .await?;
4819 context.emit_event(EventType::ChatModified(id));
4820 Ok(())
4821}
4822
4823#[derive(Debug, Serialize, Deserialize, PartialEq)]
4825pub(crate) enum SyncId {
4826 ContactAddr(String),
4828
4829 ContactFingerprint(String),
4831
4832 Grpid(String),
4833 Msgids(Vec<String>),
4835
4836 Device,
4838}
4839
4840#[derive(Debug, Serialize, Deserialize, PartialEq)]
4842pub(crate) enum SyncAction {
4843 Block,
4844 Unblock,
4845 Accept,
4846 SetVisibility(ChatVisibility),
4847 SetMuted(MuteDuration),
4848 CreateOutBroadcast {
4850 chat_name: String,
4851 secret: String,
4852 },
4853 CreateGroupEncrypted(String),
4855 Rename(String),
4856 SetContacts(Vec<String>),
4858 SetPgpContacts(Vec<(String, String)>),
4862 Delete,
4863}
4864
4865impl Context {
4866 pub(crate) async fn sync_alter_chat(&self, id: &SyncId, action: &SyncAction) -> Result<()> {
4868 let chat_id = match id {
4869 SyncId::ContactAddr(addr) => {
4870 if let SyncAction::Rename(to) = action {
4871 Contact::create_ex(self, Nosync, to, addr).await?;
4872 return Ok(());
4873 }
4874 let addr = ContactAddress::new(addr).context("Invalid address")?;
4875 let (contact_id, _) =
4876 Contact::add_or_lookup(self, "", &addr, Origin::Hidden).await?;
4877 match action {
4878 SyncAction::Block => {
4879 return contact::set_blocked(self, Nosync, contact_id, true).await;
4880 }
4881 SyncAction::Unblock => {
4882 return contact::set_blocked(self, Nosync, contact_id, false).await;
4883 }
4884 _ => (),
4885 }
4886 ChatIdBlocked::get_for_contact(self, contact_id, Blocked::Request)
4889 .await?
4890 .id
4891 }
4892 SyncId::ContactFingerprint(fingerprint) => {
4893 let name = "";
4894 let addr = "";
4895 let (contact_id, _) =
4896 Contact::add_or_lookup_ex(self, name, addr, fingerprint, Origin::Hidden)
4897 .await?;
4898 match action {
4899 SyncAction::Rename(to) => {
4900 contact_id.set_name_ex(self, Nosync, to).await?;
4901 self.emit_event(EventType::ContactsChanged(Some(contact_id)));
4902 return Ok(());
4903 }
4904 SyncAction::Block => {
4905 return contact::set_blocked(self, Nosync, contact_id, true).await;
4906 }
4907 SyncAction::Unblock => {
4908 return contact::set_blocked(self, Nosync, contact_id, false).await;
4909 }
4910 _ => (),
4911 }
4912 ChatIdBlocked::get_for_contact(self, contact_id, Blocked::Request)
4913 .await?
4914 .id
4915 }
4916 SyncId::Grpid(grpid) => {
4917 match action {
4918 SyncAction::CreateOutBroadcast { chat_name, secret } => {
4919 create_out_broadcast_ex(
4920 self,
4921 Nosync,
4922 grpid.to_string(),
4923 chat_name.clone(),
4924 secret.to_string(),
4925 )
4926 .await?;
4927 return Ok(());
4928 }
4929 SyncAction::CreateGroupEncrypted(name) => {
4930 create_group_ex(self, Nosync, grpid.clone(), name).await?;
4931 return Ok(());
4932 }
4933 _ => {}
4934 }
4935 get_chat_id_by_grpid(self, grpid)
4936 .await?
4937 .with_context(|| format!("No chat for grpid '{grpid}'"))?
4938 .0
4939 }
4940 SyncId::Msgids(msgids) => {
4941 let msg = message::get_by_rfc724_mids(self, msgids)
4942 .await?
4943 .with_context(|| format!("No message found for Message-IDs {msgids:?}"))?;
4944 ChatId::lookup_by_message(&msg)
4945 .with_context(|| format!("No chat found for Message-IDs {msgids:?}"))?
4946 }
4947 SyncId::Device => ChatId::get_for_contact(self, ContactId::DEVICE).await?,
4948 };
4949 match action {
4950 SyncAction::Block => chat_id.block_ex(self, Nosync).await,
4951 SyncAction::Unblock => chat_id.unblock_ex(self, Nosync).await,
4952 SyncAction::Accept => chat_id.accept_ex(self, Nosync).await,
4953 SyncAction::SetVisibility(v) => chat_id.set_visibility_ex(self, Nosync, *v).await,
4954 SyncAction::SetMuted(duration) => set_muted_ex(self, Nosync, chat_id, *duration).await,
4955 SyncAction::CreateOutBroadcast { .. } | SyncAction::CreateGroupEncrypted(..) => {
4956 Err(anyhow!("sync_alter_chat({id:?}, {action:?}): Bad request."))
4958 }
4959 SyncAction::Rename(to) => rename_ex(self, Nosync, chat_id, to).await,
4960 SyncAction::SetContacts(addrs) => set_contacts_by_addrs(self, chat_id, addrs).await,
4961 SyncAction::SetPgpContacts(fingerprint_addrs) => {
4962 set_contacts_by_fingerprints(self, chat_id, fingerprint_addrs).await
4963 }
4964 SyncAction::Delete => chat_id.delete_ex(self, Nosync).await,
4965 }
4966 }
4967
4968 pub(crate) fn on_archived_chats_maybe_noticed(&self) {
4973 self.emit_msgs_changed_without_msg_id(DC_CHAT_ID_ARCHIVED_LINK);
4974 }
4975}
4976
4977#[cfg(test)]
4978mod chat_tests;