1use std::cmp::Reverse;
4use std::collections::{BinaryHeap, HashSet};
5use std::fmt;
6use std::path::{Path, PathBuf};
7use std::time::UNIX_EPOCH;
8
9use anyhow::{Context as _, Result, bail, ensure};
10use async_channel::{self as channel, Receiver, Sender};
11use base64::Engine as _;
12pub use deltachat_contact_tools::may_be_valid_addr;
13use deltachat_contact_tools::{
14 self as contact_tools, ContactAddress, VcardContact, addr_normalize, sanitize_name,
15 sanitize_name_and_addr,
16};
17use deltachat_derive::{FromSql, ToSql};
18use rusqlite::OptionalExtension;
19use serde::{Deserialize, Serialize};
20use tokio::task;
21use tokio::time::{Duration, timeout};
22
23use crate::blob::BlobObject;
24use crate::chat::ChatId;
25use crate::color::str_to_color;
26use crate::config::Config;
27use crate::constants::{self, Blocked, Chattype};
28use crate::context::Context;
29use crate::events::EventType;
30use crate::key::{
31 DcKey, Fingerprint, SignedPublicKey, load_self_public_key, self_fingerprint,
32 self_fingerprint_opt,
33};
34use crate::log::{LogExt, warn};
35use crate::message::MessageState;
36use crate::mimeparser::AvatarAction;
37use crate::param::{Param, Params};
38use crate::pgp::{addresses_from_public_key, merge_openpgp_certificates};
39use crate::sync::{self, Sync::*};
40use crate::tools::{SystemTime, duration_to_str, get_abs_path, normalize_text, time, to_lowercase};
41use crate::{chat, chatlist_events, ensure_and_debug_assert_ne, stock_str};
42
43const SEEN_RECENTLY_SECONDS: i64 = 600;
45
46#[derive(
51 Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
52)]
53pub struct ContactId(u32);
54
55impl ContactId {
56 pub const UNDEFINED: ContactId = ContactId::new(0);
58
59 pub const SELF: ContactId = ContactId::new(1);
63
64 pub const INFO: ContactId = ContactId::new(2);
66
67 pub const DEVICE: ContactId = ContactId::new(5);
69 pub(crate) const LAST_SPECIAL: ContactId = ContactId::new(9);
70
71 pub const DEVICE_ADDR: &'static str = "device@localhost";
75
76 pub const fn new(id: u32) -> ContactId {
78 ContactId(id)
79 }
80
81 pub fn is_special(&self) -> bool {
88 self.0 <= Self::LAST_SPECIAL.0
89 }
90
91 pub const fn to_u32(&self) -> u32 {
97 self.0
98 }
99
100 pub async fn set_name(self, context: &Context, name: &str) -> Result<()> {
107 self.set_name_ex(context, Sync, name).await
108 }
109
110 pub(crate) async fn set_name_ex(
111 self,
112 context: &Context,
113 sync: sync::Sync,
114 name: &str,
115 ) -> Result<()> {
116 let row = context
117 .sql
118 .transaction(|transaction| {
119 let authname;
120 let name_or_authname = if !name.is_empty() {
121 name
122 } else {
123 authname = transaction.query_row(
124 "SELECT authname FROM contacts WHERE id=?",
125 (self,),
126 |row| {
127 let authname: String = row.get(0)?;
128 Ok(authname)
129 },
130 )?;
131 &authname
132 };
133 let is_changed = transaction.execute(
134 "UPDATE contacts SET name=?1, name_normalized=?2 WHERE id=?3 AND name!=?1",
135 (name, normalize_text(name_or_authname), self),
136 )? > 0;
137 if is_changed {
138 update_chat_names(context, transaction, self)?;
139 let (addr, fingerprint) = transaction.query_row(
140 "SELECT addr, fingerprint FROM contacts WHERE id=?",
141 (self,),
142 |row| {
143 let addr: String = row.get(0)?;
144 let fingerprint: String = row.get(1)?;
145 Ok((addr, fingerprint))
146 },
147 )?;
148 Ok(Some((addr, fingerprint)))
149 } else {
150 Ok(None)
151 }
152 })
153 .await?;
154 if row.is_some() {
155 context.emit_event(EventType::ContactsChanged(Some(self)));
156 }
157
158 if sync.into()
159 && let Some((addr, fingerprint)) = row
160 {
161 if fingerprint.is_empty() {
162 chat::sync(
163 context,
164 chat::SyncId::ContactAddr(addr),
165 chat::SyncAction::Rename(name.to_string()),
166 )
167 .await
168 .log_err(context)
169 .ok();
170 } else {
171 chat::sync(
172 context,
173 chat::SyncId::ContactFingerprint(fingerprint),
174 chat::SyncAction::Rename(name.to_string()),
175 )
176 .await
177 .log_err(context)
178 .ok();
179 }
180 }
181 Ok(())
182 }
183
184 pub(crate) async fn mark_bot(&self, context: &Context, is_bot: bool) -> Result<()> {
186 context
187 .sql
188 .execute("UPDATE contacts SET is_bot=? WHERE id=?;", (is_bot, self.0))
189 .await?;
190 Ok(())
191 }
192
193 pub(crate) async fn regossip_keys(&self, context: &Context) -> Result<()> {
195 context
196 .sql
197 .execute(
198 "UPDATE chats
199 SET gossiped_timestamp=0
200 WHERE EXISTS (SELECT 1 FROM chats_contacts
201 WHERE chats_contacts.chat_id=chats.id
202 AND chats_contacts.contact_id=?
203 AND chats_contacts.add_timestamp >= chats_contacts.remove_timestamp)",
204 (self,),
205 )
206 .await?;
207 Ok(())
208 }
209
210 pub(crate) async fn scaleup_origin(
212 context: &Context,
213 ids: &[Self],
214 origin: Origin,
215 ) -> Result<()> {
216 context
217 .sql
218 .transaction(|transaction| {
219 let mut stmt = transaction
220 .prepare("UPDATE contacts SET origin=?1 WHERE id = ?2 AND origin < ?1")?;
221 for id in ids {
222 stmt.execute((origin, id))?;
223 }
224 Ok(())
225 })
226 .await?;
227 Ok(())
228 }
229
230 pub async fn addr(&self, context: &Context) -> Result<String> {
232 let addr = context
233 .sql
234 .query_row("SELECT addr FROM contacts WHERE id=?", (self,), |row| {
235 let addr: String = row.get(0)?;
236 Ok(addr)
237 })
238 .await?;
239 Ok(addr)
240 }
241}
242
243impl fmt::Display for ContactId {
244 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
245 if *self == ContactId::UNDEFINED {
246 write!(f, "Contact#Undefined")
247 } else if *self == ContactId::SELF {
248 write!(f, "Contact#Self")
249 } else if *self == ContactId::INFO {
250 write!(f, "Contact#Info")
251 } else if *self == ContactId::DEVICE {
252 write!(f, "Contact#Device")
253 } else if self.is_special() {
254 write!(f, "Contact#Special{}", self.0)
255 } else {
256 write!(f, "Contact#{}", self.0)
257 }
258 }
259}
260
261impl rusqlite::types::ToSql for ContactId {
263 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
264 let val = rusqlite::types::Value::Integer(i64::from(self.0));
265 let out = rusqlite::types::ToSqlOutput::Owned(val);
266 Ok(out)
267 }
268}
269
270impl rusqlite::types::FromSql for ContactId {
272 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
273 i64::column_result(value).and_then(|val| {
274 val.try_into()
275 .map(ContactId::new)
276 .map_err(|_| rusqlite::types::FromSqlError::OutOfRange(val))
277 })
278 }
279}
280
281pub async fn make_vcard(context: &Context, contacts: &[ContactId]) -> Result<String> {
283 let now = time();
284 let mut vcard_contacts = Vec::with_capacity(contacts.len());
285 for id in contacts {
286 let c = Contact::get_by_id(context, *id).await?;
287 let key = c.public_key(context).await?.map(|k| k.to_base64());
288 let profile_image = match c.get_profile_image_ex(context, false).await? {
289 None => None,
290 Some(path) => tokio::fs::read(path)
291 .await
292 .log_err(context)
293 .ok()
294 .map(|data| base64::engine::general_purpose::STANDARD.encode(data)),
295 };
296 vcard_contacts.push(VcardContact {
297 addr: c.addr,
298 authname: c.authname,
299 key,
300 profile_image,
301 biography: Some(c.status).filter(|s| !s.is_empty()),
302 timestamp: Ok(now),
304 });
305 }
306
307 Ok(contact_tools::make_vcard(&vcard_contacts)
314 .trim_end()
315 .to_string())
316}
317
318pub(crate) async fn import_public_key(
326 context: &Context,
327 public_key: &SignedPublicKey,
328) -> Result<()> {
329 public_key
330 .verify_bindings()
331 .context("Attempt to import broken public key")?;
332
333 let fingerprint = public_key.dc_fingerprint().hex();
334
335 let merged_public_key;
336 let merged_public_key_ref = if let Some(public_key_bytes) = context
337 .sql
338 .query_row_optional(
339 "SELECT public_key
340 FROM public_keys
341 WHERE fingerprint=?",
342 (&fingerprint,),
343 |row| {
344 let bytes: Vec<u8> = row.get(0)?;
345 Ok(bytes)
346 },
347 )
348 .await?
349 {
350 let old_public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
351 merged_public_key = merge_openpgp_certificates(public_key.clone(), old_public_key)
352 .context("Failed to merge public keys")?;
353 &merged_public_key
354 } else {
355 public_key
356 };
357
358 let inserted = context
359 .sql
360 .execute(
361 "INSERT INTO public_keys (fingerprint, public_key)
362 VALUES (?, ?)
363 ON CONFLICT (fingerprint)
364 DO UPDATE SET public_key=excluded.public_key
365 WHERE public_key!=excluded.public_key",
366 (&fingerprint, merged_public_key_ref.to_bytes()),
367 )
368 .await?;
369 if inserted > 0 {
370 info!(
371 context,
372 "Saved key with fingerprint {fingerprint} from the Autocrypt header"
373 );
374 }
375
376 Ok(())
377}
378
379pub async fn import_vcard(context: &Context, vcard: &str) -> Result<Vec<ContactId>> {
384 let contacts = contact_tools::parse_vcard(vcard);
385 let mut contact_ids = Vec::with_capacity(contacts.len());
386 for c in &contacts {
387 let Ok(id) = import_vcard_contact(context, c)
388 .await
389 .with_context(|| format!("import_vcard_contact() failed for {}", c.addr))
390 .log_err(context)
391 else {
392 continue;
393 };
394 contact_ids.push(id);
395 }
396 Ok(contact_ids)
397}
398
399async fn import_vcard_contact(context: &Context, contact: &VcardContact) -> Result<ContactId> {
400 let addr = ContactAddress::new(&contact.addr).context("Invalid address")?;
401 let origin = Origin::CreateChat;
405 let key = contact.key.as_ref().and_then(|k| {
406 SignedPublicKey::from_base64(k)
407 .with_context(|| {
408 format!(
409 "import_vcard_contact: Cannot decode key for {}",
410 contact.addr
411 )
412 })
413 .log_err(context)
414 .ok()
415 });
416
417 let fingerprint = if let Some(public_key) = key {
418 import_public_key(context, &public_key)
419 .await
420 .context("Failed to import public key from vCard")?;
421 public_key.dc_fingerprint().hex()
422 } else {
423 String::new()
424 };
425
426 let (id, modified) =
427 match Contact::add_or_lookup_ex(context, &contact.authname, &addr, &fingerprint, origin)
428 .await
429 {
430 Err(e) => return Err(e).context("Contact::add_or_lookup() failed"),
431 Ok((ContactId::SELF, _)) => return Ok(ContactId::SELF),
432 Ok(val) => val,
433 };
434 if modified != Modifier::None {
435 context.emit_event(EventType::ContactsChanged(Some(id)));
436 }
437 if modified != Modifier::Created {
438 return Ok(id);
439 }
440 let path = match &contact.profile_image {
441 Some(image) => match BlobObject::store_from_base64(context, image)? {
442 None => {
443 warn!(
444 context,
445 "import_vcard_contact: Could not decode avatar for {}.", contact.addr
446 );
447 None
448 }
449 Some(path) => Some(path),
450 },
451 None => None,
452 };
453 if let Some(path) = path
454 && let Err(e) = set_profile_image(context, id, &AvatarAction::Change(path)).await
455 {
456 warn!(
457 context,
458 "import_vcard_contact: Could not set avatar for {}: {e:#}.", contact.addr
459 );
460 }
461 if let Some(biography) = &contact.biography
462 && let Err(e) = set_status(context, id, biography.to_owned()).await
463 {
464 warn!(
465 context,
466 "import_vcard_contact: Could not set biography for {}: {e:#}.", contact.addr
467 );
468 }
469 Ok(id)
470}
471
472#[derive(Debug)]
485pub struct Contact {
486 pub id: ContactId,
488
489 name: String,
493
494 authname: String,
498
499 addr: String,
501
502 fingerprint: Option<String>,
506
507 pub blocked: bool,
509
510 last_seen: i64,
512
513 pub origin: Origin,
515
516 pub param: Params,
518
519 status: String,
521
522 is_bot: bool,
524}
525
526#[derive(
528 Debug,
529 Default,
530 Clone,
531 Copy,
532 PartialEq,
533 Eq,
534 PartialOrd,
535 Ord,
536 FromPrimitive,
537 ToPrimitive,
538 FromSql,
539 ToSql,
540)]
541#[repr(u32)]
542pub enum Origin {
543 #[default]
546 Unknown = 0,
547
548 MailinglistAddress = 0x2,
550
551 Hidden = 0x8,
553
554 IncomingUnknownFrom = 0x10,
556
557 IncomingUnknownCc = 0x20,
559
560 IncomingUnknownTo = 0x40,
562
563 UnhandledQrScan = 0x80,
565
566 UnhandledSecurejoinQrScan = 0x81,
568
569 IncomingReplyTo = 0x100,
572
573 IncomingCc = 0x200,
575
576 IncomingTo = 0x400,
578
579 CreateChat = 0x800,
581
582 OutgoingBcc = 0x1000,
584
585 OutgoingCc = 0x2000,
587
588 OutgoingTo = 0x4000,
590
591 Internal = 0x40000,
593
594 AddressBook = 0x80000,
596
597 SecurejoinInvited = 0x0100_0000,
599
600 SecurejoinJoined = 0x0200_0000,
606
607 ManuallyCreated = 0x0400_0000,
609}
610
611impl Origin {
612 pub fn is_known(self) -> bool {
616 self >= Origin::IncomingReplyTo
617 }
618}
619
620#[derive(Debug, PartialEq, Eq, Clone, Copy)]
621pub(crate) enum Modifier {
622 None,
623 Modified,
624 Created,
625}
626
627impl Contact {
628 pub async fn get_by_id(context: &Context, contact_id: ContactId) -> Result<Self> {
639 let contact = Self::get_by_id_optional(context, contact_id)
640 .await?
641 .with_context(|| format!("contact {contact_id} not found"))?;
642 Ok(contact)
643 }
644
645 pub async fn get_by_id_optional(
649 context: &Context,
650 contact_id: ContactId,
651 ) -> Result<Option<Self>> {
652 if let Some(mut contact) = context
653 .sql
654 .query_row_optional(
655 "SELECT c.name, c.addr, c.origin, c.blocked, c.last_seen,
656 c.authname, c.param, c.status, c.is_bot, c.fingerprint
657 FROM contacts c
658 WHERE c.id=?;",
659 (contact_id,),
660 |row| {
661 let name: String = row.get(0)?;
662 let addr: String = row.get(1)?;
663 let origin: Origin = row.get(2)?;
664 let blocked: Option<bool> = row.get(3)?;
665 let last_seen: i64 = row.get(4)?;
666 let authname: String = row.get(5)?;
667 let param: String = row.get(6)?;
668 let status: Option<String> = row.get(7)?;
669 let is_bot: bool = row.get(8)?;
670 let fingerprint: Option<String> =
671 Some(row.get(9)?).filter(|s: &String| !s.is_empty());
672 let contact = Self {
673 id: contact_id,
674 name,
675 authname,
676 addr,
677 fingerprint,
678 blocked: blocked.unwrap_or_default(),
679 last_seen,
680 origin,
681 param: param.parse().unwrap_or_default(),
682 status: status.unwrap_or_default(),
683 is_bot,
684 };
685 Ok(contact)
686 },
687 )
688 .await?
689 {
690 if contact_id == ContactId::SELF {
691 contact.name = stock_str::self_msg(context).await;
692 contact.authname = context
693 .get_config(Config::Displayname)
694 .await?
695 .unwrap_or_default();
696 contact.addr = context
697 .get_config(Config::ConfiguredAddr)
698 .await?
699 .unwrap_or_default();
700 if let Some(self_fp) = self_fingerprint_opt(context).await? {
701 contact.fingerprint = Some(self_fp.to_string());
702 }
703 contact.status = context
704 .get_config(Config::Selfstatus)
705 .await?
706 .unwrap_or_default();
707 } else if contact_id == ContactId::DEVICE {
708 contact.name = stock_str::device_messages(context).await;
709 contact.addr = ContactId::DEVICE_ADDR.to_string();
710 contact.status = stock_str::device_messages_hint(context).await;
711 }
712 Ok(Some(contact))
713 } else {
714 Ok(None)
715 }
716 }
717
718 pub fn is_blocked(&self) -> bool {
720 self.blocked
721 }
722
723 pub fn last_seen(&self) -> i64 {
725 self.last_seen
726 }
727
728 #[expect(clippy::arithmetic_side_effects)]
730 pub fn was_seen_recently(&self) -> bool {
731 time() - self.last_seen <= SEEN_RECENTLY_SECONDS
732 }
733
734 pub async fn is_blocked_load(context: &Context, id: ContactId) -> Result<bool> {
736 let blocked = context
737 .sql
738 .query_row("SELECT blocked FROM contacts WHERE id=?", (id,), |row| {
739 let blocked: bool = row.get(0)?;
740 Ok(blocked)
741 })
742 .await?;
743 Ok(blocked)
744 }
745
746 pub async fn block(context: &Context, id: ContactId) -> Result<()> {
748 set_blocked(context, Sync, id, true).await
749 }
750
751 pub async fn unblock(context: &Context, id: ContactId) -> Result<()> {
753 set_blocked(context, Sync, id, false).await
754 }
755
756 pub async fn create(context: &Context, name: &str, addr: &str) -> Result<ContactId> {
766 Self::create_ex(context, Sync, name, addr).await
767 }
768
769 pub(crate) async fn create_ex(
770 context: &Context,
771 sync: sync::Sync,
772 name: &str,
773 addr: &str,
774 ) -> Result<ContactId> {
775 let (name, addr) = sanitize_name_and_addr(name, addr);
776 let addr = ContactAddress::new(&addr)?;
777
778 let (contact_id, sth_modified) =
779 Contact::add_or_lookup(context, &name, &addr, Origin::ManuallyCreated)
780 .await
781 .context("add_or_lookup")?;
782 let blocked = Contact::is_blocked_load(context, contact_id).await?;
783 match sth_modified {
784 Modifier::None => {}
785 Modifier::Modified | Modifier::Created => {
786 context.emit_event(EventType::ContactsChanged(Some(contact_id)))
787 }
788 }
789 if blocked {
790 set_blocked(context, Nosync, contact_id, false).await?;
791 }
792
793 if sync.into() && sth_modified != Modifier::None {
794 chat::sync(
795 context,
796 chat::SyncId::ContactAddr(addr.to_string()),
797 chat::SyncAction::Rename(name.to_string()),
798 )
799 .await
800 .log_err(context)
801 .ok();
802 }
803 Ok(contact_id)
804 }
805
806 pub async fn mark_noticed(context: &Context, id: ContactId) -> Result<()> {
808 context
809 .sql
810 .execute(
811 "UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
812 (MessageState::InNoticed, id, MessageState::InFresh),
813 )
814 .await?;
815 Ok(())
816 }
817
818 pub fn is_bot(&self) -> bool {
820 self.is_bot
821 }
822
823 pub async fn lookup_id_by_addr(
845 context: &Context,
846 addr: &str,
847 min_origin: Origin,
848 ) -> Result<Option<ContactId>> {
849 Self::lookup_id_by_addr_ex(context, addr, min_origin, Some(Blocked::Not)).await
850 }
851
852 pub(crate) async fn lookup_id_by_addr_ex(
855 context: &Context,
856 addr: &str,
857 min_origin: Origin,
858 blocked: Option<Blocked>,
859 ) -> Result<Option<ContactId>> {
860 if addr.is_empty() {
861 bail!("lookup_id_by_addr: empty address");
862 }
863
864 let addr_normalized = addr_normalize(addr);
865
866 if context.is_configured().await? && context.is_self_addr(addr).await? {
867 return Ok(Some(ContactId::SELF));
868 }
869
870 let id = context
871 .sql
872 .query_get_value(
873 "SELECT id FROM contacts
874 WHERE addr=?1 COLLATE NOCASE
875 AND id>?2 AND origin>=?3 AND (? OR blocked=?)
876 ORDER BY
877 (
878 SELECT COUNT(*) FROM chats c
879 INNER JOIN chats_contacts cc
880 ON c.id=cc.chat_id
881 WHERE c.type=?
882 AND c.id>?
883 AND c.blocked=?
884 AND cc.contact_id=contacts.id
885 ) DESC,
886 last_seen DESC, fingerprint DESC
887 LIMIT 1",
888 (
889 &addr_normalized,
890 ContactId::LAST_SPECIAL,
891 min_origin as u32,
892 blocked.is_none(),
893 blocked.unwrap_or(Blocked::Not),
894 Chattype::Single,
895 constants::DC_CHAT_ID_LAST_SPECIAL,
896 blocked.unwrap_or(Blocked::Not),
897 ),
898 )
899 .await?;
900 Ok(id)
901 }
902
903 pub(crate) async fn add_or_lookup(
904 context: &Context,
905 name: &str,
906 addr: &ContactAddress,
907 origin: Origin,
908 ) -> Result<(ContactId, Modifier)> {
909 Self::add_or_lookup_ex(context, name, addr, "", origin).await
910 }
911
912 pub(crate) async fn add_or_lookup_ex(
940 context: &Context,
941 name: &str,
942 addr: &str,
943 fingerprint: &str,
944 mut origin: Origin,
945 ) -> Result<(ContactId, Modifier)> {
946 let mut sth_modified = Modifier::None;
947
948 ensure!(
949 !addr.is_empty() || !fingerprint.is_empty(),
950 "Can not add_or_lookup empty address"
951 );
952 ensure!(origin != Origin::Unknown, "Missing valid origin");
953
954 if context.is_configured().await? && context.is_self_addr(addr).await? {
955 return Ok((ContactId::SELF, sth_modified));
956 }
957
958 if !fingerprint.is_empty() && context.is_configured().await? {
959 let fingerprint_self = self_fingerprint(context)
960 .await
961 .context("self_fingerprint")?;
962 if fingerprint == fingerprint_self {
963 return Ok((ContactId::SELF, sth_modified));
964 }
965 }
966
967 let mut name = sanitize_name(name);
968 if origin <= Origin::OutgoingTo {
969 if addr.contains("noreply")
971 || addr.contains("no-reply")
972 || addr.starts_with("notifications@")
973 || (addr.len() > 50 && addr.contains('+'))
975 {
976 info!(context, "hiding contact {}", addr);
977 origin = Origin::Hidden;
978 name = "".to_string();
982 }
983 }
984
985 let manual = matches!(
990 origin,
991 Origin::ManuallyCreated | Origin::AddressBook | Origin::UnhandledQrScan
992 );
993
994 let mut update_addr = false;
995
996 let row_id = context
997 .sql
998 .transaction(|transaction| {
999 let row = transaction
1000 .query_row(
1001 "SELECT id, name, addr, origin, authname
1002 FROM contacts
1003 WHERE fingerprint=?1 AND
1004 (?1<>'' OR addr=?2 COLLATE NOCASE)",
1005 (fingerprint, addr),
1006 |row| {
1007 let row_id: u32 = row.get(0)?;
1008 let row_name: String = row.get(1)?;
1009 let row_addr: String = row.get(2)?;
1010 let row_origin: Origin = row.get(3)?;
1011 let row_authname: String = row.get(4)?;
1012
1013 Ok((row_id, row_name, row_addr, row_origin, row_authname))
1014 },
1015 )
1016 .optional()?;
1017
1018 let row_id;
1019 if let Some((id, row_name, row_addr, row_origin, row_authname)) = row {
1020 let update_name = manual && name != row_name;
1021 let update_authname = !manual
1022 && name != row_authname
1023 && !name.is_empty()
1024 && (origin >= row_origin
1025 || origin == Origin::IncomingUnknownFrom
1026 || row_authname.is_empty());
1027
1028 row_id = id;
1029 if origin >= row_origin && addr != row_addr {
1030 update_addr = true;
1031 }
1032 if update_name || update_authname || update_addr || origin > row_origin {
1033 let new_name = if update_name {
1034 name.to_string()
1035 } else {
1036 row_name
1037 };
1038 let new_authname = if update_authname {
1039 name.to_string()
1040 } else {
1041 row_authname
1042 };
1043
1044 transaction.execute(
1045 "UPDATE contacts SET name=?, name_normalized=?, addr=?, origin=?, authname=? WHERE id=?",
1046 (
1047 &new_name,
1048 normalize_text(
1049 if !new_name.is_empty() {
1050 &new_name
1051 } else {
1052 &new_authname
1053 }),
1054 if update_addr {
1055 addr.to_string()
1056 } else {
1057 row_addr
1058 },
1059 if origin > row_origin {
1060 origin
1061 } else {
1062 row_origin
1063 },
1064 &new_authname,
1065 row_id,
1066 ),
1067 )?;
1068
1069 if update_name || update_authname {
1070 let contact_id = ContactId::new(row_id);
1071 update_chat_names(context, transaction, contact_id)?;
1072 }
1073 sth_modified = Modifier::Modified;
1074 }
1075 } else {
1076 transaction.execute(
1077 "
1078INSERT INTO contacts (name, name_normalized, addr, fingerprint, origin, authname)
1079VALUES (?, ?, ?, ?, ?, ?)
1080 ",
1081 (
1082 if manual { &name } else { "" },
1083 normalize_text(&name),
1084 &addr,
1085 fingerprint,
1086 origin,
1087 if manual { "" } else { &name },
1088 ),
1089 )?;
1090
1091 sth_modified = Modifier::Created;
1092 row_id = u32::try_from(transaction.last_insert_rowid())?;
1093 if fingerprint.is_empty() {
1094 info!(context, "Added contact id={row_id} addr={addr}.");
1095 } else {
1096 info!(
1097 context,
1098 "Added contact id={row_id} fpr={fingerprint} addr={addr}."
1099 );
1100 }
1101 }
1102 Ok(row_id)
1103 })
1104 .await?;
1105
1106 let contact_id = ContactId::new(row_id);
1107
1108 Ok((contact_id, sth_modified))
1109 }
1110
1111 #[expect(clippy::arithmetic_side_effects)]
1129 pub async fn add_address_book(context: &Context, addr_book: &str) -> Result<usize> {
1130 let mut modify_cnt = 0;
1131
1132 for (name, addr) in split_address_book(addr_book) {
1133 let (name, addr) = sanitize_name_and_addr(name, addr);
1134 match ContactAddress::new(&addr) {
1135 Ok(addr) => {
1136 match Contact::add_or_lookup(context, &name, &addr, Origin::AddressBook).await {
1137 Ok((_, modified)) => {
1138 if modified != Modifier::None {
1139 modify_cnt += 1
1140 }
1141 }
1142 Err(err) => {
1143 warn!(
1144 context,
1145 "Failed to add address {} from address book: {}", addr, err
1146 );
1147 }
1148 }
1149 }
1150 Err(err) => {
1151 warn!(context, "{:#}.", err);
1152 }
1153 }
1154 }
1155 if modify_cnt > 0 {
1156 context.emit_event(EventType::ContactsChanged(None));
1157 }
1158
1159 Ok(modify_cnt)
1160 }
1161
1162 pub async fn get_all(
1172 context: &Context,
1173 listflags: u32,
1174 query: Option<&str>,
1175 ) -> Result<Vec<ContactId>> {
1176 let self_addrs = context
1177 .get_all_self_addrs()
1178 .await?
1179 .into_iter()
1180 .collect::<HashSet<_>>();
1181 let mut add_self = false;
1182 let mut ret = Vec::new();
1183 let flag_add_self = (listflags & constants::DC_GCL_ADD_SELF) != 0;
1184 let flag_address = (listflags & constants::DC_GCL_ADDRESS) != 0;
1185 let minimal_origin = if context.get_config_bool(Config::Bot).await? {
1186 Origin::Unknown
1187 } else {
1188 Origin::IncomingReplyTo
1189 };
1190 if query.is_some() {
1191 let query_lowercased = query.unwrap_or("").to_lowercase();
1192 let s3str_like_cmd = format!("%{}%", query_lowercased);
1193 context
1194 .sql
1195 .query_map(
1196 "
1197SELECT c.id, c.addr FROM contacts c
1198WHERE c.id>?
1199 AND (c.fingerprint='')=?
1200 AND c.origin>=?
1201 AND c.blocked=0
1202 AND (IFNULL(c.name_normalized,IIF(c.name='',c.authname,c.name)) LIKE ? OR c.addr LIKE ?)
1203ORDER BY c.origin>=? DESC, c.last_seen DESC, c.id DESC
1204 ",
1205 (
1206 ContactId::LAST_SPECIAL,
1207 flag_address,
1208 minimal_origin,
1209 &s3str_like_cmd,
1210 &query_lowercased,
1211 Origin::CreateChat,
1212 ),
1213 |row| {
1214 let id: ContactId = row.get(0)?;
1215 let addr: String = row.get(1)?;
1216 Ok((id, addr))
1217 },
1218 |rows| {
1219 for row in rows {
1220 let (id, addr) = row?;
1221 if !self_addrs.contains(&addr) {
1222 ret.push(id);
1223 }
1224 }
1225 Ok(())
1226 },
1227 )
1228 .await?;
1229
1230 if let Some(query) = query {
1231 let self_addr = context
1232 .get_config(Config::ConfiguredAddr)
1233 .await?
1234 .unwrap_or_default();
1235 let self_name = context
1236 .get_config(Config::Displayname)
1237 .await?
1238 .unwrap_or_default();
1239 let self_name2 = stock_str::self_msg(context);
1240
1241 if self_addr.contains(query)
1242 || self_name.contains(query)
1243 || self_name2.await.contains(query)
1244 {
1245 add_self = true;
1246 }
1247 } else {
1248 add_self = true;
1249 }
1250 } else {
1251 add_self = true;
1252
1253 context
1254 .sql
1255 .query_map(
1256 "SELECT id, addr FROM contacts
1257 WHERE id>?
1258 AND (fingerprint='')=?
1259 AND origin>=?
1260 AND blocked=0
1261 ORDER BY origin>=? DESC, last_seen DESC, id DESC",
1262 (
1263 ContactId::LAST_SPECIAL,
1264 flag_address,
1265 minimal_origin,
1266 Origin::CreateChat,
1267 ),
1268 |row| {
1269 let id: ContactId = row.get(0)?;
1270 let addr: String = row.get(1)?;
1271 Ok((id, addr))
1272 },
1273 |rows| {
1274 for row in rows {
1275 let (id, addr) = row?;
1276 if !self_addrs.contains(&addr) {
1277 ret.push(id);
1278 }
1279 }
1280 Ok(())
1281 },
1282 )
1283 .await?;
1284 }
1285
1286 if flag_add_self && add_self {
1287 ret.push(ContactId::SELF);
1288 }
1289
1290 Ok(ret)
1291 }
1292
1293 async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
1299 context
1300 .sql
1301 .transaction(move |transaction| {
1302 let mut stmt = transaction.prepare(
1303 "SELECT name, grpid, type FROM chats WHERE (type=? OR type=?) AND blocked=?",
1304 )?;
1305 let rows = stmt.query_map(
1306 (Chattype::Mailinglist, Chattype::InBroadcast, Blocked::Yes),
1307 |row| {
1308 let name: String = row.get(0)?;
1309 let grpid: String = row.get(1)?;
1310 let typ: Chattype = row.get(2)?;
1311 Ok((name, grpid, typ))
1312 },
1313 )?;
1314 let blocked_mailinglists = rows.collect::<std::result::Result<Vec<_>, _>>()?;
1315 for (name, grpid, typ) in blocked_mailinglists {
1316 let count = transaction.query_row(
1317 "SELECT COUNT(id) FROM contacts WHERE addr=?",
1318 [&grpid],
1319 |row| {
1320 let count: isize = row.get(0)?;
1321 Ok(count)
1322 },
1323 )?;
1324 if count == 0 {
1325 transaction.execute("INSERT INTO contacts (addr) VALUES (?)", [&grpid])?;
1326 }
1327
1328 let fingerprint = if typ == Chattype::InBroadcast {
1329 "Blocked_broadcast"
1332 } else {
1333 ""
1334 };
1335 transaction.execute(
1337 "
1338UPDATE contacts
1339SET name=?, name_normalized=IIF(?1='',name_normalized,?), origin=?, blocked=1, fingerprint=?
1340WHERE addr=?
1341 ",
1342 (
1343 &name,
1344 normalize_text(&name),
1345 Origin::MailinglistAddress,
1346 fingerprint,
1347 &grpid,
1348 ),
1349 )?;
1350 }
1351 Ok(())
1352 })
1353 .await?;
1354 Ok(())
1355 }
1356
1357 pub async fn get_all_blocked(context: &Context) -> Result<Vec<ContactId>> {
1359 Contact::update_blocked_mailinglist_contacts(context)
1360 .await
1361 .context("cannot update blocked mailinglist contacts")?;
1362
1363 let list = context
1364 .sql
1365 .query_map_vec(
1366 "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY last_seen DESC, id DESC;",
1367 (ContactId::LAST_SPECIAL,),
1368 |row| {
1369 let contact_id: ContactId = row.get(0)?;
1370 Ok(contact_id)
1371 }
1372 )
1373 .await?;
1374 Ok(list)
1375 }
1376
1377 pub async fn get_encrinfo(context: &Context, contact_id: ContactId) -> Result<String> {
1383 ensure!(
1384 !contact_id.is_special(),
1385 "Can not provide encryption info for special contact"
1386 );
1387
1388 let contact = Contact::get_by_id(context, contact_id).await?;
1389 let addr = context
1390 .get_config(Config::ConfiguredAddr)
1391 .await?
1392 .unwrap_or_default();
1393
1394 let Some(fingerprint_other) = contact.fingerprint() else {
1395 return Ok(stock_str::encr_none(context).await);
1396 };
1397 let fingerprint_other = fingerprint_other.to_string();
1398
1399 let stock_message = if contact.public_key(context).await?.is_some() {
1400 stock_str::messages_are_e2ee(context).await
1401 } else {
1402 stock_str::encr_none(context).await
1403 };
1404
1405 let finger_prints = stock_str::finger_prints(context).await;
1406 let mut ret = format!("{stock_message}\n{finger_prints}:");
1407
1408 let fingerprint_self = load_self_public_key(context)
1409 .await?
1410 .dc_fingerprint()
1411 .to_string();
1412 if addr < contact.addr {
1413 cat_fingerprint(
1414 &mut ret,
1415 &stock_str::self_msg(context).await,
1416 &addr,
1417 &fingerprint_self,
1418 );
1419 cat_fingerprint(
1420 &mut ret,
1421 contact.get_display_name(),
1422 &contact.addr,
1423 &fingerprint_other,
1424 );
1425 } else {
1426 cat_fingerprint(
1427 &mut ret,
1428 contact.get_display_name(),
1429 &contact.addr,
1430 &fingerprint_other,
1431 );
1432 cat_fingerprint(
1433 &mut ret,
1434 &stock_str::self_msg(context).await,
1435 &addr,
1436 &fingerprint_self,
1437 );
1438 }
1439
1440 if let Some(public_key) = contact.public_key(context).await?
1441 && let Some(relay_addrs) = addresses_from_public_key(&public_key)
1442 {
1443 ret += "\n\nRelays:";
1444 for relay in &relay_addrs {
1445 ret += "\n";
1446 ret += relay;
1447 }
1448 }
1449
1450 Ok(ret)
1451 }
1452
1453 pub async fn delete(context: &Context, contact_id: ContactId) -> Result<()> {
1459 ensure!(!contact_id.is_special(), "Can not delete special contact");
1460
1461 context
1462 .sql
1463 .transaction(move |transaction| {
1464 let deleted_contacts = transaction.execute(
1467 "DELETE FROM contacts WHERE id=?
1468 AND (SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?)=0;",
1469 (contact_id, contact_id),
1470 )?;
1471 if deleted_contacts == 0 {
1472 transaction.execute(
1473 "UPDATE contacts SET origin=? WHERE id=?;",
1474 (Origin::Hidden, contact_id),
1475 )?;
1476 }
1477 Ok(())
1478 })
1479 .await?;
1480
1481 context.emit_event(EventType::ContactsChanged(None));
1482 Ok(())
1483 }
1484
1485 pub async fn update_param(&self, context: &Context) -> Result<()> {
1487 context
1488 .sql
1489 .execute(
1490 "UPDATE contacts SET param=? WHERE id=?",
1491 (self.param.to_string(), self.id),
1492 )
1493 .await?;
1494 Ok(())
1495 }
1496
1497 pub async fn update_status(&self, context: &Context) -> Result<()> {
1499 context
1500 .sql
1501 .execute(
1502 "UPDATE contacts SET status=? WHERE id=?",
1503 (&self.status, self.id),
1504 )
1505 .await?;
1506 Ok(())
1507 }
1508
1509 pub fn get_id(&self) -> ContactId {
1511 self.id
1512 }
1513
1514 pub fn get_addr(&self) -> &str {
1516 &self.addr
1517 }
1518
1519 pub fn is_key_contact(&self) -> bool {
1522 self.fingerprint.is_some() || self.id == ContactId::SELF
1523 }
1524
1525 pub fn fingerprint(&self) -> Option<Fingerprint> {
1529 if let Some(fingerprint) = &self.fingerprint {
1530 fingerprint.parse().ok()
1531 } else {
1532 None
1533 }
1534 }
1535
1536 pub async fn public_key(&self, context: &Context) -> Result<Option<SignedPublicKey>> {
1543 if self.id == ContactId::SELF {
1544 return Ok(Some(load_self_public_key(context).await?));
1545 }
1546
1547 if let Some(fingerprint) = &self.fingerprint {
1548 if let Some(public_key_bytes) = context
1549 .sql
1550 .query_row_optional(
1551 "SELECT public_key
1552 FROM public_keys
1553 WHERE fingerprint=?",
1554 (fingerprint,),
1555 |row| {
1556 let bytes: Vec<u8> = row.get(0)?;
1557 Ok(bytes)
1558 },
1559 )
1560 .await?
1561 {
1562 let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
1563 Ok(Some(public_key))
1564 } else {
1565 Ok(None)
1566 }
1567 } else {
1568 Ok(None)
1569 }
1570 }
1571
1572 pub fn get_authname(&self) -> &str {
1574 &self.authname
1575 }
1576
1577 pub fn get_name(&self) -> &str {
1583 &self.name
1584 }
1585
1586 pub fn get_display_name(&self) -> &str {
1592 if !self.name.is_empty() {
1593 return &self.name;
1594 }
1595 if !self.authname.is_empty() {
1596 return &self.authname;
1597 }
1598 &self.addr
1599 }
1600
1601 pub fn get_name_n_addr(&self) -> String {
1612 if !self.name.is_empty() {
1613 format!("{} ({})", self.name, self.addr)
1614 } else if !self.authname.is_empty() {
1615 format!("{} ({})", self.authname, self.addr)
1616 } else {
1617 (&self.addr).into()
1618 }
1619 }
1620
1621 pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1625 self.get_profile_image_ex(context, true).await
1626 }
1627
1628 async fn get_profile_image_ex(
1632 &self,
1633 context: &Context,
1634 show_fallback_icon: bool,
1635 ) -> Result<Option<PathBuf>> {
1636 if self.id == ContactId::SELF {
1637 if let Some(p) = context.get_config(Config::Selfavatar).await? {
1638 return Ok(Some(PathBuf::from(p))); }
1640 } else if self.id == ContactId::DEVICE {
1641 return Ok(Some(chat::get_device_icon(context).await?));
1642 }
1643 if show_fallback_icon && !self.id.is_special() && !self.is_key_contact() {
1644 return Ok(Some(chat::get_unencrypted_icon(context).await?));
1645 }
1646 if let Some(image_rel) = self.param.get(Param::ProfileImage)
1647 && !image_rel.is_empty()
1648 {
1649 return Ok(Some(get_abs_path(context, Path::new(image_rel))));
1650 }
1651 Ok(None)
1652 }
1653
1654 pub fn get_color(&self) -> u32 {
1658 get_color(self.id == ContactId::SELF, &self.addr, &self.fingerprint())
1659 }
1660
1661 pub async fn get_or_gen_color(&self, context: &Context) -> Result<u32> {
1666 let mut fpr = self.fingerprint();
1667 if fpr.is_none() && self.id == ContactId::SELF {
1668 fpr = Some(load_self_public_key(context).await?.dc_fingerprint());
1669 }
1670 Ok(get_color(self.id == ContactId::SELF, &self.addr, &fpr))
1671 }
1672
1673 pub fn get_status(&self) -> &str {
1677 self.status.as_str()
1678 }
1679
1680 pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1682 if self.id == ContactId::SELF {
1683 return Ok(true);
1685 }
1686 Ok(self.public_key(context).await?.is_some())
1687 }
1688
1689 pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1702 if self.id == ContactId::SELF {
1705 return Ok(true);
1706 }
1707
1708 Ok(self.get_verifier_id(context).await?.is_some())
1709 }
1710
1711 pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<Option<ContactId>>> {
1720 let verifier_id: u32 = context
1721 .sql
1722 .query_get_value("SELECT verifier FROM contacts WHERE id=?", (self.id,))
1723 .await?
1724 .with_context(|| format!("Contact {} does not exist", self.id))?;
1725
1726 if verifier_id == 0 {
1727 Ok(None)
1728 } else if verifier_id == self.id.to_u32() {
1729 Ok(Some(None))
1730 } else {
1731 Ok(Some(Some(ContactId::new(verifier_id))))
1732 }
1733 }
1734
1735 pub async fn get_real_cnt(context: &Context) -> Result<usize> {
1737 if !context.sql.is_open().await {
1738 return Ok(0);
1739 }
1740
1741 let count = context
1742 .sql
1743 .count(
1744 "SELECT COUNT(*) FROM contacts WHERE id>?;",
1745 (ContactId::LAST_SPECIAL,),
1746 )
1747 .await?;
1748 Ok(count)
1749 }
1750
1751 pub async fn real_exists_by_id(context: &Context, contact_id: ContactId) -> Result<bool> {
1753 if contact_id.is_special() {
1754 return Ok(false);
1755 }
1756
1757 let exists = context
1758 .sql
1759 .exists("SELECT COUNT(*) FROM contacts WHERE id=?;", (contact_id,))
1760 .await?;
1761 Ok(exists)
1762 }
1763}
1764
1765pub fn get_color(is_self: bool, addr: &str, fingerprint: &Option<Fingerprint>) -> u32 {
1771 if let Some(fingerprint) = fingerprint {
1772 str_to_color(&fingerprint.hex())
1773 } else if is_self {
1774 0x808080
1775 } else {
1776 str_to_color(&to_lowercase(addr))
1777 }
1778}
1779
1780fn update_chat_names(
1784 context: &Context,
1785 transaction: &rusqlite::Connection,
1786 contact_id: ContactId,
1787) -> Result<()> {
1788 let chat_id: Option<ChatId> = transaction.query_row(
1789 "SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
1790 (Chattype::Single, contact_id),
1791 |row| {
1792 let chat_id: ChatId = row.get(0)?;
1793 Ok(chat_id)
1794 }
1795 ).optional()?;
1796
1797 if let Some(chat_id) = chat_id {
1798 let (addr, name, authname) = transaction.query_row(
1799 "SELECT addr, name, authname
1800 FROM contacts
1801 WHERE id=?",
1802 (contact_id,),
1803 |row| {
1804 let addr: String = row.get(0)?;
1805 let name: String = row.get(1)?;
1806 let authname: String = row.get(2)?;
1807 Ok((addr, name, authname))
1808 },
1809 )?;
1810
1811 let chat_name = if !name.is_empty() {
1812 name
1813 } else if !authname.is_empty() {
1814 authname
1815 } else {
1816 addr
1817 };
1818
1819 let count = transaction.execute(
1820 "UPDATE chats SET name=?1, name_normalized=?2 WHERE id=?3 AND name!=?1",
1821 (&chat_name, normalize_text(&chat_name), chat_id),
1822 )?;
1823
1824 if count > 0 {
1825 context.emit_event(EventType::ChatModified(chat_id));
1827 chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
1828 }
1829 }
1830
1831 Ok(())
1832}
1833
1834pub(crate) async fn set_blocked(
1835 context: &Context,
1836 sync: sync::Sync,
1837 contact_id: ContactId,
1838 new_blocking: bool,
1839) -> Result<()> {
1840 ensure!(
1841 !contact_id.is_special(),
1842 "Can't block special contact {contact_id}"
1843 );
1844 let contact = Contact::get_by_id(context, contact_id).await?;
1845
1846 if contact.blocked != new_blocking {
1847 context
1848 .sql
1849 .execute(
1850 "UPDATE contacts SET blocked=? WHERE id=?;",
1851 (i32::from(new_blocking), contact_id),
1852 )
1853 .await?;
1854
1855 if context
1861 .sql
1862 .execute(
1863 r#"
1864UPDATE chats
1865SET blocked=?
1866WHERE type=? AND id IN (
1867 SELECT chat_id FROM chats_contacts WHERE contact_id=?
1868);
1869"#,
1870 (new_blocking, Chattype::Single, contact_id),
1871 )
1872 .await
1873 .is_ok()
1874 {
1875 Contact::mark_noticed(context, contact_id).await?;
1876 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1877 }
1878
1879 if !new_blocking
1882 && contact.origin == Origin::MailinglistAddress
1883 && let Some((chat_id, ..)) = chat::get_chat_id_by_grpid(context, &contact.addr).await?
1884 {
1885 chat_id.unblock_ex(context, Nosync).await?;
1886 }
1887
1888 if sync.into() {
1889 let action = match new_blocking {
1890 true => chat::SyncAction::Block,
1891 false => chat::SyncAction::Unblock,
1892 };
1893 let sync_id = if let Some(fingerprint) = contact.fingerprint() {
1894 chat::SyncId::ContactFingerprint(fingerprint.hex())
1895 } else {
1896 chat::SyncId::ContactAddr(contact.addr.clone())
1897 };
1898
1899 chat::sync(context, sync_id, action)
1900 .await
1901 .log_err(context)
1902 .ok();
1903 }
1904 }
1905
1906 chatlist_events::emit_chatlist_changed(context);
1907 Ok(())
1908}
1909
1910pub(crate) async fn set_profile_image(
1917 context: &Context,
1918 contact_id: ContactId,
1919 profile_image: &AvatarAction,
1920) -> Result<()> {
1921 let mut contact = Contact::get_by_id(context, contact_id).await?;
1922 let changed = match profile_image {
1923 AvatarAction::Change(profile_image) => {
1924 if contact_id == ContactId::SELF {
1925 context
1926 .set_config_ex(Nosync, Config::Selfavatar, Some(profile_image))
1927 .await?;
1928 } else {
1929 contact.param.set(Param::ProfileImage, profile_image);
1930 }
1931 true
1932 }
1933 AvatarAction::Delete => {
1934 if contact_id == ContactId::SELF {
1935 context
1936 .set_config_ex(Nosync, Config::Selfavatar, None)
1937 .await?;
1938 } else {
1939 contact.param.remove(Param::ProfileImage);
1940 }
1941 true
1942 }
1943 };
1944 if changed {
1945 contact.update_param(context).await?;
1946 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1947 chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await;
1948 }
1949 Ok(())
1950}
1951
1952pub(crate) async fn set_status(
1956 context: &Context,
1957 contact_id: ContactId,
1958 status: String,
1959) -> Result<()> {
1960 if contact_id == ContactId::SELF {
1961 context
1962 .set_config_ex(Nosync, Config::Selfstatus, Some(&status))
1963 .await?;
1964 } else {
1965 let mut contact = Contact::get_by_id(context, contact_id).await?;
1966
1967 if contact.status != status {
1968 contact.status = status;
1969 contact.update_status(context).await?;
1970 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1971 }
1972 }
1973 Ok(())
1974}
1975
1976#[expect(clippy::arithmetic_side_effects)]
1978pub(crate) async fn update_last_seen(
1979 context: &Context,
1980 contact_id: ContactId,
1981 timestamp: i64,
1982) -> Result<()> {
1983 ensure!(
1984 !contact_id.is_special(),
1985 "Can not update special contact last seen timestamp"
1986 );
1987
1988 if context
1989 .sql
1990 .execute(
1991 "UPDATE contacts SET last_seen = ?1 WHERE last_seen < ?1 AND id = ?2",
1992 (timestamp, contact_id),
1993 )
1994 .await?
1995 > 0
1996 && timestamp > time() - SEEN_RECENTLY_SECONDS
1997 {
1998 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1999 context
2000 .scheduler
2001 .interrupt_recently_seen(contact_id, timestamp)
2002 .await;
2003 }
2004 Ok(())
2005}
2006
2007pub(crate) async fn mark_contact_id_as_verified(
2011 context: &Context,
2012 contact_id: ContactId,
2013 verifier_id: Option<ContactId>,
2014) -> Result<()> {
2015 ensure_and_debug_assert_ne!(contact_id, ContactId::SELF,);
2016 ensure_and_debug_assert_ne!(
2017 Some(contact_id),
2018 verifier_id,
2019 "Contact cannot be verified by self",
2020 );
2021 let by_self = verifier_id == Some(ContactId::SELF);
2022 let mut verifier_id = verifier_id.unwrap_or(contact_id);
2023 context
2024 .sql
2025 .transaction(|transaction| {
2026 let contact_fingerprint: String = transaction.query_row(
2027 "SELECT fingerprint FROM contacts WHERE id=?",
2028 (contact_id,),
2029 |row| row.get(0),
2030 )?;
2031 if contact_fingerprint.is_empty() {
2032 bail!("Non-key-contact {contact_id} cannot be verified");
2033 }
2034 if verifier_id != ContactId::SELF {
2035 let (verifier_fingerprint, verifier_verifier_id): (String, ContactId) = transaction
2036 .query_row(
2037 "SELECT fingerprint, verifier FROM contacts WHERE id=?",
2038 (verifier_id,),
2039 |row| Ok((row.get(0)?, row.get(1)?)),
2040 )?;
2041 if verifier_fingerprint.is_empty() {
2042 bail!(
2043 "Contact {contact_id} cannot be verified by non-key-contact {verifier_id}"
2044 );
2045 }
2046 ensure!(
2047 verifier_id == contact_id || verifier_verifier_id != ContactId::UNDEFINED,
2048 "Contact {contact_id} cannot be verified by unverified contact {verifier_id}",
2049 );
2050 if verifier_verifier_id == verifier_id {
2051 verifier_id = contact_id;
2056 }
2057 }
2058 transaction.execute(
2059 "UPDATE contacts SET verifier=?1
2060 WHERE id=?2 AND (verifier=0 OR verifier=id OR ?3)",
2061 (verifier_id, contact_id, by_self),
2062 )?;
2063 Ok(())
2064 })
2065 .await?;
2066 Ok(())
2067}
2068
2069#[expect(clippy::arithmetic_side_effects)]
2070fn cat_fingerprint(ret: &mut String, name: &str, addr: &str, fingerprint: &str) {
2071 *ret += &format!("\n\n{name} ({addr}):\n{fingerprint}");
2072}
2073
2074fn split_address_book(book: &str) -> Vec<(&str, &str)> {
2075 book.lines()
2076 .collect::<Vec<&str>>()
2077 .chunks(2)
2078 .filter_map(|chunk| {
2079 let name = chunk.first()?;
2080 let addr = chunk.get(1)?;
2081 Some((*name, *addr))
2082 })
2083 .collect()
2084}
2085
2086#[derive(Debug)]
2087pub(crate) struct RecentlySeenInterrupt {
2088 contact_id: ContactId,
2089 timestamp: i64,
2090}
2091
2092#[derive(Debug)]
2093pub(crate) struct RecentlySeenLoop {
2094 handle: task::JoinHandle<()>,
2096
2097 interrupt_send: Sender<RecentlySeenInterrupt>,
2098}
2099
2100impl RecentlySeenLoop {
2101 pub(crate) fn new(context: Context) -> Self {
2102 let (interrupt_send, interrupt_recv) = channel::bounded(1);
2103
2104 let handle = task::spawn(Self::run(context, interrupt_recv));
2105 Self {
2106 handle,
2107 interrupt_send,
2108 }
2109 }
2110
2111 #[expect(clippy::arithmetic_side_effects)]
2112 async fn run(context: Context, interrupt: Receiver<RecentlySeenInterrupt>) {
2113 type MyHeapElem = (Reverse<i64>, ContactId);
2114
2115 let now = SystemTime::now();
2116 let now_ts = now
2117 .duration_since(SystemTime::UNIX_EPOCH)
2118 .unwrap_or_default()
2119 .as_secs() as i64;
2120
2121 let mut unseen_queue: BinaryHeap<MyHeapElem> = context
2127 .sql
2128 .query_map_collect(
2129 "SELECT id, last_seen FROM contacts
2130 WHERE last_seen > ?",
2131 (now_ts - SEEN_RECENTLY_SECONDS,),
2132 |row| {
2133 let contact_id: ContactId = row.get("id")?;
2134 let last_seen: i64 = row.get("last_seen")?;
2135 Ok((Reverse(last_seen + SEEN_RECENTLY_SECONDS), contact_id))
2136 },
2137 )
2138 .await
2139 .unwrap_or_default();
2140
2141 loop {
2142 let now = SystemTime::now();
2143 let (until, contact_id) =
2144 if let Some((Reverse(timestamp), contact_id)) = unseen_queue.peek() {
2145 (
2146 UNIX_EPOCH
2147 + Duration::from_secs((*timestamp).try_into().unwrap_or(u64::MAX))
2148 + Duration::from_secs(1),
2149 Some(contact_id),
2150 )
2151 } else {
2152 (now + Duration::from_secs(86400), None)
2154 };
2155
2156 if let Ok(duration) = until.duration_since(now) {
2157 info!(
2158 context,
2159 "Recently seen loop waiting for {} or interrupt",
2160 duration_to_str(duration)
2161 );
2162
2163 match timeout(duration, interrupt.recv()).await {
2164 Err(_) => {
2165 if let Some(contact_id) = contact_id {
2167 context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2168 chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2169 &context,
2170 *contact_id,
2171 )
2172 .await;
2173 unseen_queue.pop();
2174 }
2175 }
2176 Ok(Err(err)) => {
2177 warn!(
2178 context,
2179 "Error receiving an interruption in recently seen loop: {}", err
2180 );
2181 return;
2184 }
2185 Ok(Ok(RecentlySeenInterrupt {
2186 contact_id,
2187 timestamp,
2188 })) => {
2189 if contact_id != ContactId::UNDEFINED {
2191 unseen_queue
2192 .push((Reverse(timestamp + SEEN_RECENTLY_SECONDS), contact_id));
2193 }
2194 }
2195 }
2196 } else {
2197 info!(
2198 context,
2199 "Recently seen loop is not waiting, event is already due."
2200 );
2201
2202 if let Some(contact_id) = contact_id {
2204 context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2205 chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2206 &context,
2207 *contact_id,
2208 )
2209 .await;
2210 }
2211 unseen_queue.pop();
2212 }
2213 }
2214 }
2215
2216 pub(crate) fn try_interrupt(&self, contact_id: ContactId, timestamp: i64) {
2217 self.interrupt_send
2218 .try_send(RecentlySeenInterrupt {
2219 contact_id,
2220 timestamp,
2221 })
2222 .ok();
2223 }
2224
2225 #[cfg(test)]
2226 pub(crate) async fn interrupt(&self, contact_id: ContactId, timestamp: i64) {
2227 self.interrupt_send
2228 .send(RecentlySeenInterrupt {
2229 contact_id,
2230 timestamp,
2231 })
2232 .await
2233 .unwrap();
2234 }
2235
2236 pub(crate) async fn abort(self) {
2237 self.handle.abort();
2238
2239 self.handle.await.ok();
2243 }
2244}
2245
2246#[cfg(test)]
2247mod contact_tests;