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