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::sync::{self, Sync::*};
39use crate::tools::{SystemTime, duration_to_str, get_abs_path, normalize_text, time, to_lowercase};
40use crate::{chat, chatlist_events, ensure_and_debug_assert_ne, stock_str};
41
42const SEEN_RECENTLY_SECONDS: i64 = 600;
44
45#[derive(
50 Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
51)]
52pub struct ContactId(u32);
53
54impl ContactId {
55 pub const UNDEFINED: ContactId = ContactId::new(0);
57
58 pub const SELF: ContactId = ContactId::new(1);
62
63 pub const INFO: ContactId = ContactId::new(2);
65
66 pub const DEVICE: ContactId = ContactId::new(5);
68 pub(crate) const LAST_SPECIAL: ContactId = ContactId::new(9);
69
70 pub const DEVICE_ADDR: &'static str = "device@localhost";
74
75 pub const fn new(id: u32) -> ContactId {
77 ContactId(id)
78 }
79
80 pub fn is_special(&self) -> bool {
87 self.0 <= Self::LAST_SPECIAL.0
88 }
89
90 pub const fn to_u32(&self) -> u32 {
96 self.0
97 }
98
99 pub async fn set_name(self, context: &Context, name: &str) -> Result<()> {
106 self.set_name_ex(context, Sync, name).await
107 }
108
109 pub(crate) async fn set_name_ex(
110 self,
111 context: &Context,
112 sync: sync::Sync,
113 name: &str,
114 ) -> Result<()> {
115 let row = context
116 .sql
117 .transaction(|transaction| {
118 let authname;
119 let name_or_authname = if !name.is_empty() {
120 name
121 } else {
122 authname = transaction.query_row(
123 "SELECT authname FROM contacts WHERE id=?",
124 (self,),
125 |row| {
126 let authname: String = row.get(0)?;
127 Ok(authname)
128 },
129 )?;
130 &authname
131 };
132 let is_changed = transaction.execute(
133 "UPDATE contacts SET name=?1, name_normalized=?2 WHERE id=?3 AND name!=?1",
134 (name, normalize_text(name_or_authname), self),
135 )? > 0;
136 if is_changed {
137 update_chat_names(context, transaction, self)?;
138 let (addr, fingerprint) = transaction.query_row(
139 "SELECT addr, fingerprint FROM contacts WHERE id=?",
140 (self,),
141 |row| {
142 let addr: String = row.get(0)?;
143 let fingerprint: String = row.get(1)?;
144 Ok((addr, fingerprint))
145 },
146 )?;
147 Ok(Some((addr, fingerprint)))
148 } else {
149 Ok(None)
150 }
151 })
152 .await?;
153 if row.is_some() {
154 context.emit_event(EventType::ContactsChanged(Some(self)));
155 }
156
157 if sync.into()
158 && let Some((addr, fingerprint)) = row
159 {
160 if fingerprint.is_empty() {
161 chat::sync(
162 context,
163 chat::SyncId::ContactAddr(addr),
164 chat::SyncAction::Rename(name.to_string()),
165 )
166 .await
167 .log_err(context)
168 .ok();
169 } else {
170 chat::sync(
171 context,
172 chat::SyncId::ContactFingerprint(fingerprint),
173 chat::SyncAction::Rename(name.to_string()),
174 )
175 .await
176 .log_err(context)
177 .ok();
178 }
179 }
180 Ok(())
181 }
182
183 pub(crate) async fn mark_bot(&self, context: &Context, is_bot: bool) -> Result<()> {
185 context
186 .sql
187 .execute("UPDATE contacts SET is_bot=? WHERE id=?;", (is_bot, self.0))
188 .await?;
189 Ok(())
190 }
191
192 pub(crate) async fn regossip_keys(&self, context: &Context) -> Result<()> {
194 context
195 .sql
196 .execute(
197 "UPDATE chats
198 SET gossiped_timestamp=0
199 WHERE EXISTS (SELECT 1 FROM chats_contacts
200 WHERE chats_contacts.chat_id=chats.id
201 AND chats_contacts.contact_id=?
202 AND chats_contacts.add_timestamp >= chats_contacts.remove_timestamp)",
203 (self,),
204 )
205 .await?;
206 Ok(())
207 }
208
209 pub(crate) async fn scaleup_origin(
211 context: &Context,
212 ids: &[Self],
213 origin: Origin,
214 ) -> Result<()> {
215 context
216 .sql
217 .transaction(|transaction| {
218 let mut stmt = transaction
219 .prepare("UPDATE contacts SET origin=?1 WHERE id = ?2 AND origin < ?1")?;
220 for id in ids {
221 stmt.execute((origin, id))?;
222 }
223 Ok(())
224 })
225 .await?;
226 Ok(())
227 }
228
229 pub async fn addr(&self, context: &Context) -> Result<String> {
231 let addr = context
232 .sql
233 .query_row("SELECT addr FROM contacts WHERE id=?", (self,), |row| {
234 let addr: String = row.get(0)?;
235 Ok(addr)
236 })
237 .await?;
238 Ok(addr)
239 }
240}
241
242impl fmt::Display for ContactId {
243 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244 if *self == ContactId::UNDEFINED {
245 write!(f, "Contact#Undefined")
246 } else if *self == ContactId::SELF {
247 write!(f, "Contact#Self")
248 } else if *self == ContactId::INFO {
249 write!(f, "Contact#Info")
250 } else if *self == ContactId::DEVICE {
251 write!(f, "Contact#Device")
252 } else if self.is_special() {
253 write!(f, "Contact#Special{}", self.0)
254 } else {
255 write!(f, "Contact#{}", self.0)
256 }
257 }
258}
259
260impl rusqlite::types::ToSql for ContactId {
262 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
263 let val = rusqlite::types::Value::Integer(i64::from(self.0));
264 let out = rusqlite::types::ToSqlOutput::Owned(val);
265 Ok(out)
266 }
267}
268
269impl rusqlite::types::FromSql for ContactId {
271 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
272 i64::column_result(value).and_then(|val| {
273 val.try_into()
274 .map(ContactId::new)
275 .map_err(|_| rusqlite::types::FromSqlError::OutOfRange(val))
276 })
277 }
278}
279
280pub async fn make_vcard(context: &Context, contacts: &[ContactId]) -> Result<String> {
282 let now = time();
283 let mut vcard_contacts = Vec::with_capacity(contacts.len());
284 for id in contacts {
285 let c = Contact::get_by_id(context, *id).await?;
286 let key = c.public_key(context).await?.map(|k| k.to_base64());
287 let profile_image = match c.get_profile_image_ex(context, false).await? {
288 None => None,
289 Some(path) => tokio::fs::read(path)
290 .await
291 .log_err(context)
292 .ok()
293 .map(|data| base64::engine::general_purpose::STANDARD.encode(data)),
294 };
295 vcard_contacts.push(VcardContact {
296 addr: c.addr,
297 authname: c.authname,
298 key,
299 profile_image,
300 biography: Some(c.status).filter(|s| !s.is_empty()),
301 timestamp: Ok(now),
303 });
304 }
305
306 Ok(contact_tools::make_vcard(&vcard_contacts)
313 .trim_end()
314 .to_string())
315}
316
317pub async fn import_vcard(context: &Context, vcard: &str) -> Result<Vec<ContactId>> {
322 let contacts = contact_tools::parse_vcard(vcard);
323 let mut contact_ids = Vec::with_capacity(contacts.len());
324 for c in &contacts {
325 let Ok(id) = import_vcard_contact(context, c)
326 .await
327 .with_context(|| format!("import_vcard_contact() failed for {}", c.addr))
328 .log_err(context)
329 else {
330 continue;
331 };
332 contact_ids.push(id);
333 }
334 Ok(contact_ids)
335}
336
337async fn import_vcard_contact(context: &Context, contact: &VcardContact) -> Result<ContactId> {
338 let addr = ContactAddress::new(&contact.addr).context("Invalid address")?;
339 let origin = Origin::CreateChat;
343 let key = contact.key.as_ref().and_then(|k| {
344 SignedPublicKey::from_base64(k)
345 .with_context(|| {
346 format!(
347 "import_vcard_contact: Cannot decode key for {}",
348 contact.addr
349 )
350 })
351 .log_err(context)
352 .ok()
353 });
354
355 let fingerprint;
356 if let Some(public_key) = key {
357 fingerprint = public_key.dc_fingerprint().hex();
358
359 context
360 .sql
361 .execute(
362 "INSERT INTO public_keys (fingerprint, public_key)
363 VALUES (?, ?)
364 ON CONFLICT (fingerprint)
365 DO NOTHING",
366 (&fingerprint, public_key.to_bytes()),
367 )
368 .await?;
369 } else {
370 fingerprint = String::new();
371 }
372
373 let (id, modified) =
374 match Contact::add_or_lookup_ex(context, &contact.authname, &addr, &fingerprint, origin)
375 .await
376 {
377 Err(e) => return Err(e).context("Contact::add_or_lookup() failed"),
378 Ok((ContactId::SELF, _)) => return Ok(ContactId::SELF),
379 Ok(val) => val,
380 };
381 if modified != Modifier::None {
382 context.emit_event(EventType::ContactsChanged(Some(id)));
383 }
384 if modified != Modifier::Created {
385 return Ok(id);
386 }
387 let path = match &contact.profile_image {
388 Some(image) => match BlobObject::store_from_base64(context, image)? {
389 None => {
390 warn!(
391 context,
392 "import_vcard_contact: Could not decode avatar for {}.", contact.addr
393 );
394 None
395 }
396 Some(path) => Some(path),
397 },
398 None => None,
399 };
400 if let Some(path) = path
401 && let Err(e) = set_profile_image(context, id, &AvatarAction::Change(path)).await
402 {
403 warn!(
404 context,
405 "import_vcard_contact: Could not set avatar for {}: {e:#}.", contact.addr
406 );
407 }
408 if let Some(biography) = &contact.biography
409 && let Err(e) = set_status(context, id, biography.to_owned()).await
410 {
411 warn!(
412 context,
413 "import_vcard_contact: Could not set biography for {}: {e:#}.", contact.addr
414 );
415 }
416 Ok(id)
417}
418
419#[derive(Debug)]
432pub struct Contact {
433 pub id: ContactId,
435
436 name: String,
440
441 authname: String,
445
446 addr: String,
448
449 fingerprint: Option<String>,
453
454 pub blocked: bool,
456
457 last_seen: i64,
459
460 pub origin: Origin,
462
463 pub param: Params,
465
466 status: String,
468
469 is_bot: bool,
471}
472
473#[derive(
475 Debug,
476 Default,
477 Clone,
478 Copy,
479 PartialEq,
480 Eq,
481 PartialOrd,
482 Ord,
483 FromPrimitive,
484 ToPrimitive,
485 FromSql,
486 ToSql,
487)]
488#[repr(u32)]
489pub enum Origin {
490 #[default]
493 Unknown = 0,
494
495 MailinglistAddress = 0x2,
497
498 Hidden = 0x8,
500
501 IncomingUnknownFrom = 0x10,
503
504 IncomingUnknownCc = 0x20,
506
507 IncomingUnknownTo = 0x40,
509
510 UnhandledQrScan = 0x80,
512
513 UnhandledSecurejoinQrScan = 0x81,
515
516 IncomingReplyTo = 0x100,
519
520 IncomingCc = 0x200,
522
523 IncomingTo = 0x400,
525
526 CreateChat = 0x800,
528
529 OutgoingBcc = 0x1000,
531
532 OutgoingCc = 0x2000,
534
535 OutgoingTo = 0x4000,
537
538 Internal = 0x40000,
540
541 AddressBook = 0x80000,
543
544 SecurejoinInvited = 0x0100_0000,
546
547 SecurejoinJoined = 0x0200_0000,
553
554 ManuallyCreated = 0x0400_0000,
556}
557
558impl Origin {
559 pub fn is_known(self) -> bool {
563 self >= Origin::IncomingReplyTo
564 }
565}
566
567#[derive(Debug, PartialEq, Eq, Clone, Copy)]
568pub(crate) enum Modifier {
569 None,
570 Modified,
571 Created,
572}
573
574impl Contact {
575 pub async fn get_by_id(context: &Context, contact_id: ContactId) -> Result<Self> {
586 let contact = Self::get_by_id_optional(context, contact_id)
587 .await?
588 .with_context(|| format!("contact {contact_id} not found"))?;
589 Ok(contact)
590 }
591
592 pub async fn get_by_id_optional(
596 context: &Context,
597 contact_id: ContactId,
598 ) -> Result<Option<Self>> {
599 if let Some(mut contact) = context
600 .sql
601 .query_row_optional(
602 "SELECT c.name, c.addr, c.origin, c.blocked, c.last_seen,
603 c.authname, c.param, c.status, c.is_bot, c.fingerprint
604 FROM contacts c
605 WHERE c.id=?;",
606 (contact_id,),
607 |row| {
608 let name: String = row.get(0)?;
609 let addr: String = row.get(1)?;
610 let origin: Origin = row.get(2)?;
611 let blocked: Option<bool> = row.get(3)?;
612 let last_seen: i64 = row.get(4)?;
613 let authname: String = row.get(5)?;
614 let param: String = row.get(6)?;
615 let status: Option<String> = row.get(7)?;
616 let is_bot: bool = row.get(8)?;
617 let fingerprint: Option<String> =
618 Some(row.get(9)?).filter(|s: &String| !s.is_empty());
619 let contact = Self {
620 id: contact_id,
621 name,
622 authname,
623 addr,
624 fingerprint,
625 blocked: blocked.unwrap_or_default(),
626 last_seen,
627 origin,
628 param: param.parse().unwrap_or_default(),
629 status: status.unwrap_or_default(),
630 is_bot,
631 };
632 Ok(contact)
633 },
634 )
635 .await?
636 {
637 if contact_id == ContactId::SELF {
638 contact.name = stock_str::self_msg(context).await;
639 contact.authname = context
640 .get_config(Config::Displayname)
641 .await?
642 .unwrap_or_default();
643 contact.addr = context
644 .get_config(Config::ConfiguredAddr)
645 .await?
646 .unwrap_or_default();
647 if let Some(self_fp) = self_fingerprint_opt(context).await? {
648 contact.fingerprint = Some(self_fp.to_string());
649 }
650 contact.status = context
651 .get_config(Config::Selfstatus)
652 .await?
653 .unwrap_or_default();
654 } else if contact_id == ContactId::DEVICE {
655 contact.name = stock_str::device_messages(context).await;
656 contact.addr = ContactId::DEVICE_ADDR.to_string();
657 contact.status = stock_str::device_messages_hint(context).await;
658 }
659 Ok(Some(contact))
660 } else {
661 Ok(None)
662 }
663 }
664
665 pub fn is_blocked(&self) -> bool {
667 self.blocked
668 }
669
670 pub fn last_seen(&self) -> i64 {
672 self.last_seen
673 }
674
675 #[expect(clippy::arithmetic_side_effects)]
677 pub fn was_seen_recently(&self) -> bool {
678 time() - self.last_seen <= SEEN_RECENTLY_SECONDS
679 }
680
681 pub async fn is_blocked_load(context: &Context, id: ContactId) -> Result<bool> {
683 let blocked = context
684 .sql
685 .query_row("SELECT blocked FROM contacts WHERE id=?", (id,), |row| {
686 let blocked: bool = row.get(0)?;
687 Ok(blocked)
688 })
689 .await?;
690 Ok(blocked)
691 }
692
693 pub async fn block(context: &Context, id: ContactId) -> Result<()> {
695 set_blocked(context, Sync, id, true).await
696 }
697
698 pub async fn unblock(context: &Context, id: ContactId) -> Result<()> {
700 set_blocked(context, Sync, id, false).await
701 }
702
703 pub async fn create(context: &Context, name: &str, addr: &str) -> Result<ContactId> {
713 Self::create_ex(context, Sync, name, addr).await
714 }
715
716 pub(crate) async fn create_ex(
717 context: &Context,
718 sync: sync::Sync,
719 name: &str,
720 addr: &str,
721 ) -> Result<ContactId> {
722 let (name, addr) = sanitize_name_and_addr(name, addr);
723 let addr = ContactAddress::new(&addr)?;
724
725 let (contact_id, sth_modified) =
726 Contact::add_or_lookup(context, &name, &addr, Origin::ManuallyCreated)
727 .await
728 .context("add_or_lookup")?;
729 let blocked = Contact::is_blocked_load(context, contact_id).await?;
730 match sth_modified {
731 Modifier::None => {}
732 Modifier::Modified | Modifier::Created => {
733 context.emit_event(EventType::ContactsChanged(Some(contact_id)))
734 }
735 }
736 if blocked {
737 set_blocked(context, Nosync, contact_id, false).await?;
738 }
739
740 if sync.into() && sth_modified != Modifier::None {
741 chat::sync(
742 context,
743 chat::SyncId::ContactAddr(addr.to_string()),
744 chat::SyncAction::Rename(name.to_string()),
745 )
746 .await
747 .log_err(context)
748 .ok();
749 }
750 Ok(contact_id)
751 }
752
753 pub async fn mark_noticed(context: &Context, id: ContactId) -> Result<()> {
755 context
756 .sql
757 .execute(
758 "UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
759 (MessageState::InNoticed, id, MessageState::InFresh),
760 )
761 .await?;
762 Ok(())
763 }
764
765 pub fn is_bot(&self) -> bool {
767 self.is_bot
768 }
769
770 pub async fn lookup_id_by_addr(
792 context: &Context,
793 addr: &str,
794 min_origin: Origin,
795 ) -> Result<Option<ContactId>> {
796 Self::lookup_id_by_addr_ex(context, addr, min_origin, Some(Blocked::Not)).await
797 }
798
799 pub(crate) async fn lookup_id_by_addr_ex(
802 context: &Context,
803 addr: &str,
804 min_origin: Origin,
805 blocked: Option<Blocked>,
806 ) -> Result<Option<ContactId>> {
807 if addr.is_empty() {
808 bail!("lookup_id_by_addr: empty address");
809 }
810
811 let addr_normalized = addr_normalize(addr);
812
813 if context.is_configured().await? && context.is_self_addr(addr).await? {
814 return Ok(Some(ContactId::SELF));
815 }
816
817 let id = context
818 .sql
819 .query_get_value(
820 "SELECT id FROM contacts
821 WHERE addr=?1 COLLATE NOCASE
822 AND id>?2 AND origin>=?3 AND (? OR blocked=?)
823 ORDER BY
824 (
825 SELECT COUNT(*) FROM chats c
826 INNER JOIN chats_contacts cc
827 ON c.id=cc.chat_id
828 WHERE c.type=?
829 AND c.id>?
830 AND c.blocked=?
831 AND cc.contact_id=contacts.id
832 ) DESC,
833 last_seen DESC, fingerprint DESC
834 LIMIT 1",
835 (
836 &addr_normalized,
837 ContactId::LAST_SPECIAL,
838 min_origin as u32,
839 blocked.is_none(),
840 blocked.unwrap_or(Blocked::Not),
841 Chattype::Single,
842 constants::DC_CHAT_ID_LAST_SPECIAL,
843 blocked.unwrap_or(Blocked::Not),
844 ),
845 )
846 .await?;
847 Ok(id)
848 }
849
850 pub(crate) async fn add_or_lookup(
851 context: &Context,
852 name: &str,
853 addr: &ContactAddress,
854 origin: Origin,
855 ) -> Result<(ContactId, Modifier)> {
856 Self::add_or_lookup_ex(context, name, addr, "", origin).await
857 }
858
859 pub(crate) async fn add_or_lookup_ex(
887 context: &Context,
888 name: &str,
889 addr: &str,
890 fingerprint: &str,
891 mut origin: Origin,
892 ) -> Result<(ContactId, Modifier)> {
893 let mut sth_modified = Modifier::None;
894
895 ensure!(
896 !addr.is_empty() || !fingerprint.is_empty(),
897 "Can not add_or_lookup empty address"
898 );
899 ensure!(origin != Origin::Unknown, "Missing valid origin");
900
901 if context.is_configured().await? && context.is_self_addr(addr).await? {
902 return Ok((ContactId::SELF, sth_modified));
903 }
904
905 if !fingerprint.is_empty() && context.is_configured().await? {
906 let fingerprint_self = self_fingerprint(context)
907 .await
908 .context("self_fingerprint")?;
909 if fingerprint == fingerprint_self {
910 return Ok((ContactId::SELF, sth_modified));
911 }
912 }
913
914 let mut name = sanitize_name(name);
915 if origin <= Origin::OutgoingTo {
916 if addr.contains("noreply")
918 || addr.contains("no-reply")
919 || addr.starts_with("notifications@")
920 || (addr.len() > 50 && addr.contains('+'))
922 {
923 info!(context, "hiding contact {}", addr);
924 origin = Origin::Hidden;
925 name = "".to_string();
929 }
930 }
931
932 let manual = matches!(
937 origin,
938 Origin::ManuallyCreated | Origin::AddressBook | Origin::UnhandledQrScan
939 );
940
941 let mut update_addr = false;
942
943 let row_id = context
944 .sql
945 .transaction(|transaction| {
946 let row = transaction
947 .query_row(
948 "SELECT id, name, addr, origin, authname
949 FROM contacts
950 WHERE fingerprint=?1 AND
951 (?1<>'' OR addr=?2 COLLATE NOCASE)",
952 (fingerprint, addr),
953 |row| {
954 let row_id: u32 = row.get(0)?;
955 let row_name: String = row.get(1)?;
956 let row_addr: String = row.get(2)?;
957 let row_origin: Origin = row.get(3)?;
958 let row_authname: String = row.get(4)?;
959
960 Ok((row_id, row_name, row_addr, row_origin, row_authname))
961 },
962 )
963 .optional()?;
964
965 let row_id;
966 if let Some((id, row_name, row_addr, row_origin, row_authname)) = row {
967 let update_name = manual && name != row_name;
968 let update_authname = !manual
969 && name != row_authname
970 && !name.is_empty()
971 && (origin >= row_origin
972 || origin == Origin::IncomingUnknownFrom
973 || row_authname.is_empty());
974
975 row_id = id;
976 if origin >= row_origin && addr != row_addr {
977 update_addr = true;
978 }
979 if update_name || update_authname || update_addr || origin > row_origin {
980 let new_name = if update_name {
981 name.to_string()
982 } else {
983 row_name
984 };
985 let new_authname = if update_authname {
986 name.to_string()
987 } else {
988 row_authname
989 };
990
991 transaction.execute(
992 "UPDATE contacts SET name=?, name_normalized=?, addr=?, origin=?, authname=? WHERE id=?",
993 (
994 &new_name,
995 normalize_text(
996 if !new_name.is_empty() {
997 &new_name
998 } else {
999 &new_authname
1000 }),
1001 if update_addr {
1002 addr.to_string()
1003 } else {
1004 row_addr
1005 },
1006 if origin > row_origin {
1007 origin
1008 } else {
1009 row_origin
1010 },
1011 &new_authname,
1012 row_id,
1013 ),
1014 )?;
1015
1016 if update_name || update_authname {
1017 let contact_id = ContactId::new(row_id);
1018 update_chat_names(context, transaction, contact_id)?;
1019 }
1020 sth_modified = Modifier::Modified;
1021 }
1022 } else {
1023 transaction.execute(
1024 "
1025INSERT INTO contacts (name, name_normalized, addr, fingerprint, origin, authname)
1026VALUES (?, ?, ?, ?, ?, ?)
1027 ",
1028 (
1029 if manual { &name } else { "" },
1030 normalize_text(&name),
1031 &addr,
1032 fingerprint,
1033 origin,
1034 if manual { "" } else { &name },
1035 ),
1036 )?;
1037
1038 sth_modified = Modifier::Created;
1039 row_id = u32::try_from(transaction.last_insert_rowid())?;
1040 if fingerprint.is_empty() {
1041 info!(context, "Added contact id={row_id} addr={addr}.");
1042 } else {
1043 info!(
1044 context,
1045 "Added contact id={row_id} fpr={fingerprint} addr={addr}."
1046 );
1047 }
1048 }
1049 Ok(row_id)
1050 })
1051 .await?;
1052
1053 let contact_id = ContactId::new(row_id);
1054
1055 Ok((contact_id, sth_modified))
1056 }
1057
1058 #[expect(clippy::arithmetic_side_effects)]
1076 pub async fn add_address_book(context: &Context, addr_book: &str) -> Result<usize> {
1077 let mut modify_cnt = 0;
1078
1079 for (name, addr) in split_address_book(addr_book) {
1080 let (name, addr) = sanitize_name_and_addr(name, addr);
1081 match ContactAddress::new(&addr) {
1082 Ok(addr) => {
1083 match Contact::add_or_lookup(context, &name, &addr, Origin::AddressBook).await {
1084 Ok((_, modified)) => {
1085 if modified != Modifier::None {
1086 modify_cnt += 1
1087 }
1088 }
1089 Err(err) => {
1090 warn!(
1091 context,
1092 "Failed to add address {} from address book: {}", addr, err
1093 );
1094 }
1095 }
1096 }
1097 Err(err) => {
1098 warn!(context, "{:#}.", err);
1099 }
1100 }
1101 }
1102 if modify_cnt > 0 {
1103 context.emit_event(EventType::ContactsChanged(None));
1104 }
1105
1106 Ok(modify_cnt)
1107 }
1108
1109 pub async fn get_all(
1119 context: &Context,
1120 listflags: u32,
1121 query: Option<&str>,
1122 ) -> Result<Vec<ContactId>> {
1123 let self_addrs = context
1124 .get_all_self_addrs()
1125 .await?
1126 .into_iter()
1127 .collect::<HashSet<_>>();
1128 let mut add_self = false;
1129 let mut ret = Vec::new();
1130 let flag_add_self = (listflags & constants::DC_GCL_ADD_SELF) != 0;
1131 let flag_address = (listflags & constants::DC_GCL_ADDRESS) != 0;
1132 let minimal_origin = if context.get_config_bool(Config::Bot).await? {
1133 Origin::Unknown
1134 } else {
1135 Origin::IncomingReplyTo
1136 };
1137 if query.is_some() {
1138 let query_lowercased = query.unwrap_or("").to_lowercase();
1139 let s3str_like_cmd = format!("%{}%", query_lowercased);
1140 context
1141 .sql
1142 .query_map(
1143 "
1144SELECT c.id, c.addr FROM contacts c
1145WHERE c.id>?
1146 AND (c.fingerprint='')=?
1147 AND c.origin>=?
1148 AND c.blocked=0
1149 AND (IFNULL(c.name_normalized,IIF(c.name='',c.authname,c.name)) LIKE ? OR c.addr LIKE ?)
1150ORDER BY c.origin>=? DESC, c.last_seen DESC, c.id DESC
1151 ",
1152 (
1153 ContactId::LAST_SPECIAL,
1154 flag_address,
1155 minimal_origin,
1156 &s3str_like_cmd,
1157 &query_lowercased,
1158 Origin::CreateChat,
1159 ),
1160 |row| {
1161 let id: ContactId = row.get(0)?;
1162 let addr: String = row.get(1)?;
1163 Ok((id, addr))
1164 },
1165 |rows| {
1166 for row in rows {
1167 let (id, addr) = row?;
1168 if !self_addrs.contains(&addr) {
1169 ret.push(id);
1170 }
1171 }
1172 Ok(())
1173 },
1174 )
1175 .await?;
1176
1177 if let Some(query) = query {
1178 let self_addr = context
1179 .get_config(Config::ConfiguredAddr)
1180 .await?
1181 .unwrap_or_default();
1182 let self_name = context
1183 .get_config(Config::Displayname)
1184 .await?
1185 .unwrap_or_default();
1186 let self_name2 = stock_str::self_msg(context);
1187
1188 if self_addr.contains(query)
1189 || self_name.contains(query)
1190 || self_name2.await.contains(query)
1191 {
1192 add_self = true;
1193 }
1194 } else {
1195 add_self = true;
1196 }
1197 } else {
1198 add_self = true;
1199
1200 context
1201 .sql
1202 .query_map(
1203 "SELECT id, addr FROM contacts
1204 WHERE id>?
1205 AND (fingerprint='')=?
1206 AND origin>=?
1207 AND blocked=0
1208 ORDER BY origin>=? DESC, last_seen DESC, id DESC",
1209 (
1210 ContactId::LAST_SPECIAL,
1211 flag_address,
1212 minimal_origin,
1213 Origin::CreateChat,
1214 ),
1215 |row| {
1216 let id: ContactId = row.get(0)?;
1217 let addr: String = row.get(1)?;
1218 Ok((id, addr))
1219 },
1220 |rows| {
1221 for row in rows {
1222 let (id, addr) = row?;
1223 if !self_addrs.contains(&addr) {
1224 ret.push(id);
1225 }
1226 }
1227 Ok(())
1228 },
1229 )
1230 .await?;
1231 }
1232
1233 if flag_add_self && add_self {
1234 ret.push(ContactId::SELF);
1235 }
1236
1237 Ok(ret)
1238 }
1239
1240 async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
1246 context
1247 .sql
1248 .transaction(move |transaction| {
1249 let mut stmt = transaction.prepare(
1250 "SELECT name, grpid, type FROM chats WHERE (type=? OR type=?) AND blocked=?",
1251 )?;
1252 let rows = stmt.query_map(
1253 (Chattype::Mailinglist, Chattype::InBroadcast, Blocked::Yes),
1254 |row| {
1255 let name: String = row.get(0)?;
1256 let grpid: String = row.get(1)?;
1257 let typ: Chattype = row.get(2)?;
1258 Ok((name, grpid, typ))
1259 },
1260 )?;
1261 let blocked_mailinglists = rows.collect::<std::result::Result<Vec<_>, _>>()?;
1262 for (name, grpid, typ) in blocked_mailinglists {
1263 let count = transaction.query_row(
1264 "SELECT COUNT(id) FROM contacts WHERE addr=?",
1265 [&grpid],
1266 |row| {
1267 let count: isize = row.get(0)?;
1268 Ok(count)
1269 },
1270 )?;
1271 if count == 0 {
1272 transaction.execute("INSERT INTO contacts (addr) VALUES (?)", [&grpid])?;
1273 }
1274
1275 let fingerprint = if typ == Chattype::InBroadcast {
1276 "Blocked_broadcast"
1279 } else {
1280 ""
1281 };
1282 transaction.execute(
1284 "
1285UPDATE contacts
1286SET name=?, name_normalized=IIF(?1='',name_normalized,?), origin=?, blocked=1, fingerprint=?
1287WHERE addr=?
1288 ",
1289 (
1290 &name,
1291 normalize_text(&name),
1292 Origin::MailinglistAddress,
1293 fingerprint,
1294 &grpid,
1295 ),
1296 )?;
1297 }
1298 Ok(())
1299 })
1300 .await?;
1301 Ok(())
1302 }
1303
1304 pub async fn get_all_blocked(context: &Context) -> Result<Vec<ContactId>> {
1306 Contact::update_blocked_mailinglist_contacts(context)
1307 .await
1308 .context("cannot update blocked mailinglist contacts")?;
1309
1310 let list = context
1311 .sql
1312 .query_map_vec(
1313 "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY last_seen DESC, id DESC;",
1314 (ContactId::LAST_SPECIAL,),
1315 |row| {
1316 let contact_id: ContactId = row.get(0)?;
1317 Ok(contact_id)
1318 }
1319 )
1320 .await?;
1321 Ok(list)
1322 }
1323
1324 pub async fn get_encrinfo(context: &Context, contact_id: ContactId) -> Result<String> {
1330 ensure!(
1331 !contact_id.is_special(),
1332 "Can not provide encryption info for special contact"
1333 );
1334
1335 let contact = Contact::get_by_id(context, contact_id).await?;
1336 let addr = context
1337 .get_config(Config::ConfiguredAddr)
1338 .await?
1339 .unwrap_or_default();
1340
1341 let Some(fingerprint_other) = contact.fingerprint() else {
1342 return Ok(stock_str::encr_none(context).await);
1343 };
1344 let fingerprint_other = fingerprint_other.to_string();
1345
1346 let stock_message = if contact.public_key(context).await?.is_some() {
1347 stock_str::messages_e2e_encrypted(context).await
1348 } else {
1349 stock_str::encr_none(context).await
1350 };
1351
1352 let finger_prints = stock_str::finger_prints(context).await;
1353 let mut ret = format!("{stock_message}\n{finger_prints}:");
1354
1355 let fingerprint_self = load_self_public_key(context)
1356 .await?
1357 .dc_fingerprint()
1358 .to_string();
1359 if addr < contact.addr {
1360 cat_fingerprint(
1361 &mut ret,
1362 &stock_str::self_msg(context).await,
1363 &addr,
1364 &fingerprint_self,
1365 );
1366 cat_fingerprint(
1367 &mut ret,
1368 contact.get_display_name(),
1369 &contact.addr,
1370 &fingerprint_other,
1371 );
1372 } else {
1373 cat_fingerprint(
1374 &mut ret,
1375 contact.get_display_name(),
1376 &contact.addr,
1377 &fingerprint_other,
1378 );
1379 cat_fingerprint(
1380 &mut ret,
1381 &stock_str::self_msg(context).await,
1382 &addr,
1383 &fingerprint_self,
1384 );
1385 }
1386
1387 Ok(ret)
1388 }
1389
1390 pub async fn delete(context: &Context, contact_id: ContactId) -> Result<()> {
1396 ensure!(!contact_id.is_special(), "Can not delete special contact");
1397
1398 context
1399 .sql
1400 .transaction(move |transaction| {
1401 let deleted_contacts = transaction.execute(
1404 "DELETE FROM contacts WHERE id=?
1405 AND (SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?)=0;",
1406 (contact_id, contact_id),
1407 )?;
1408 if deleted_contacts == 0 {
1409 transaction.execute(
1410 "UPDATE contacts SET origin=? WHERE id=?;",
1411 (Origin::Hidden, contact_id),
1412 )?;
1413 }
1414 Ok(())
1415 })
1416 .await?;
1417
1418 context.emit_event(EventType::ContactsChanged(None));
1419 Ok(())
1420 }
1421
1422 pub async fn update_param(&self, context: &Context) -> Result<()> {
1424 context
1425 .sql
1426 .execute(
1427 "UPDATE contacts SET param=? WHERE id=?",
1428 (self.param.to_string(), self.id),
1429 )
1430 .await?;
1431 Ok(())
1432 }
1433
1434 pub async fn update_status(&self, context: &Context) -> Result<()> {
1436 context
1437 .sql
1438 .execute(
1439 "UPDATE contacts SET status=? WHERE id=?",
1440 (&self.status, self.id),
1441 )
1442 .await?;
1443 Ok(())
1444 }
1445
1446 pub fn get_id(&self) -> ContactId {
1448 self.id
1449 }
1450
1451 pub fn get_addr(&self) -> &str {
1453 &self.addr
1454 }
1455
1456 pub fn is_key_contact(&self) -> bool {
1459 self.fingerprint.is_some() || self.id == ContactId::SELF
1460 }
1461
1462 pub fn fingerprint(&self) -> Option<Fingerprint> {
1466 if let Some(fingerprint) = &self.fingerprint {
1467 fingerprint.parse().ok()
1468 } else {
1469 None
1470 }
1471 }
1472
1473 pub async fn public_key(&self, context: &Context) -> Result<Option<SignedPublicKey>> {
1480 if self.id == ContactId::SELF {
1481 return Ok(Some(load_self_public_key(context).await?));
1482 }
1483
1484 if let Some(fingerprint) = &self.fingerprint {
1485 if let Some(public_key_bytes) = context
1486 .sql
1487 .query_row_optional(
1488 "SELECT public_key
1489 FROM public_keys
1490 WHERE fingerprint=?",
1491 (fingerprint,),
1492 |row| {
1493 let bytes: Vec<u8> = row.get(0)?;
1494 Ok(bytes)
1495 },
1496 )
1497 .await?
1498 {
1499 let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
1500 Ok(Some(public_key))
1501 } else {
1502 Ok(None)
1503 }
1504 } else {
1505 Ok(None)
1506 }
1507 }
1508
1509 pub fn get_authname(&self) -> &str {
1511 &self.authname
1512 }
1513
1514 pub fn get_name(&self) -> &str {
1520 &self.name
1521 }
1522
1523 pub fn get_display_name(&self) -> &str {
1529 if !self.name.is_empty() {
1530 return &self.name;
1531 }
1532 if !self.authname.is_empty() {
1533 return &self.authname;
1534 }
1535 &self.addr
1536 }
1537
1538 pub fn get_name_n_addr(&self) -> String {
1549 if !self.name.is_empty() {
1550 format!("{} ({})", self.name, self.addr)
1551 } else if !self.authname.is_empty() {
1552 format!("{} ({})", self.authname, self.addr)
1553 } else {
1554 (&self.addr).into()
1555 }
1556 }
1557
1558 pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1562 self.get_profile_image_ex(context, true).await
1563 }
1564
1565 async fn get_profile_image_ex(
1569 &self,
1570 context: &Context,
1571 show_fallback_icon: bool,
1572 ) -> Result<Option<PathBuf>> {
1573 if self.id == ContactId::SELF {
1574 if let Some(p) = context.get_config(Config::Selfavatar).await? {
1575 return Ok(Some(PathBuf::from(p))); }
1577 } else if self.id == ContactId::DEVICE {
1578 return Ok(Some(chat::get_device_icon(context).await?));
1579 }
1580 if show_fallback_icon && !self.id.is_special() && !self.is_key_contact() {
1581 return Ok(Some(chat::get_unencrypted_icon(context).await?));
1582 }
1583 if let Some(image_rel) = self.param.get(Param::ProfileImage)
1584 && !image_rel.is_empty()
1585 {
1586 return Ok(Some(get_abs_path(context, Path::new(image_rel))));
1587 }
1588 Ok(None)
1589 }
1590
1591 pub fn get_color(&self) -> u32 {
1595 get_color(self.id == ContactId::SELF, &self.addr, &self.fingerprint())
1596 }
1597
1598 pub async fn get_or_gen_color(&self, context: &Context) -> Result<u32> {
1603 let mut fpr = self.fingerprint();
1604 if fpr.is_none() && self.id == ContactId::SELF {
1605 fpr = Some(load_self_public_key(context).await?.dc_fingerprint());
1606 }
1607 Ok(get_color(self.id == ContactId::SELF, &self.addr, &fpr))
1608 }
1609
1610 pub fn get_status(&self) -> &str {
1614 self.status.as_str()
1615 }
1616
1617 pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1619 if self.id == ContactId::SELF {
1620 return Ok(true);
1622 }
1623 Ok(self.public_key(context).await?.is_some())
1624 }
1625
1626 pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1639 if self.id == ContactId::SELF {
1642 return Ok(true);
1643 }
1644
1645 Ok(self.get_verifier_id(context).await?.is_some())
1646 }
1647
1648 pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<Option<ContactId>>> {
1657 let verifier_id: u32 = context
1658 .sql
1659 .query_get_value("SELECT verifier FROM contacts WHERE id=?", (self.id,))
1660 .await?
1661 .with_context(|| format!("Contact {} does not exist", self.id))?;
1662
1663 if verifier_id == 0 {
1664 Ok(None)
1665 } else if verifier_id == self.id.to_u32() {
1666 Ok(Some(None))
1667 } else {
1668 Ok(Some(Some(ContactId::new(verifier_id))))
1669 }
1670 }
1671
1672 pub async fn get_real_cnt(context: &Context) -> Result<usize> {
1674 if !context.sql.is_open().await {
1675 return Ok(0);
1676 }
1677
1678 let count = context
1679 .sql
1680 .count(
1681 "SELECT COUNT(*) FROM contacts WHERE id>?;",
1682 (ContactId::LAST_SPECIAL,),
1683 )
1684 .await?;
1685 Ok(count)
1686 }
1687
1688 pub async fn real_exists_by_id(context: &Context, contact_id: ContactId) -> Result<bool> {
1690 if contact_id.is_special() {
1691 return Ok(false);
1692 }
1693
1694 let exists = context
1695 .sql
1696 .exists("SELECT COUNT(*) FROM contacts WHERE id=?;", (contact_id,))
1697 .await?;
1698 Ok(exists)
1699 }
1700}
1701
1702pub fn get_color(is_self: bool, addr: &str, fingerprint: &Option<Fingerprint>) -> u32 {
1708 if let Some(fingerprint) = fingerprint {
1709 str_to_color(&fingerprint.hex())
1710 } else if is_self {
1711 0x808080
1712 } else {
1713 str_to_color(&to_lowercase(addr))
1714 }
1715}
1716
1717fn update_chat_names(
1721 context: &Context,
1722 transaction: &rusqlite::Connection,
1723 contact_id: ContactId,
1724) -> Result<()> {
1725 let chat_id: Option<ChatId> = transaction.query_row(
1726 "SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
1727 (Chattype::Single, contact_id),
1728 |row| {
1729 let chat_id: ChatId = row.get(0)?;
1730 Ok(chat_id)
1731 }
1732 ).optional()?;
1733
1734 if let Some(chat_id) = chat_id {
1735 let (addr, name, authname) = transaction.query_row(
1736 "SELECT addr, name, authname
1737 FROM contacts
1738 WHERE id=?",
1739 (contact_id,),
1740 |row| {
1741 let addr: String = row.get(0)?;
1742 let name: String = row.get(1)?;
1743 let authname: String = row.get(2)?;
1744 Ok((addr, name, authname))
1745 },
1746 )?;
1747
1748 let chat_name = if !name.is_empty() {
1749 name
1750 } else if !authname.is_empty() {
1751 authname
1752 } else {
1753 addr
1754 };
1755
1756 let count = transaction.execute(
1757 "UPDATE chats SET name=?1, name_normalized=?2 WHERE id=?3 AND name!=?1",
1758 (&chat_name, normalize_text(&chat_name), chat_id),
1759 )?;
1760
1761 if count > 0 {
1762 context.emit_event(EventType::ChatModified(chat_id));
1764 chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
1765 }
1766 }
1767
1768 Ok(())
1769}
1770
1771pub(crate) async fn set_blocked(
1772 context: &Context,
1773 sync: sync::Sync,
1774 contact_id: ContactId,
1775 new_blocking: bool,
1776) -> Result<()> {
1777 ensure!(
1778 !contact_id.is_special(),
1779 "Can't block special contact {contact_id}"
1780 );
1781 let contact = Contact::get_by_id(context, contact_id).await?;
1782
1783 if contact.blocked != new_blocking {
1784 context
1785 .sql
1786 .execute(
1787 "UPDATE contacts SET blocked=? WHERE id=?;",
1788 (i32::from(new_blocking), contact_id),
1789 )
1790 .await?;
1791
1792 if context
1798 .sql
1799 .execute(
1800 r#"
1801UPDATE chats
1802SET blocked=?
1803WHERE type=? AND id IN (
1804 SELECT chat_id FROM chats_contacts WHERE contact_id=?
1805);
1806"#,
1807 (new_blocking, Chattype::Single, contact_id),
1808 )
1809 .await
1810 .is_ok()
1811 {
1812 Contact::mark_noticed(context, contact_id).await?;
1813 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1814 }
1815
1816 if !new_blocking
1819 && contact.origin == Origin::MailinglistAddress
1820 && let Some((chat_id, ..)) = chat::get_chat_id_by_grpid(context, &contact.addr).await?
1821 {
1822 chat_id.unblock_ex(context, Nosync).await?;
1823 }
1824
1825 if sync.into() {
1826 let action = match new_blocking {
1827 true => chat::SyncAction::Block,
1828 false => chat::SyncAction::Unblock,
1829 };
1830 let sync_id = if let Some(fingerprint) = contact.fingerprint() {
1831 chat::SyncId::ContactFingerprint(fingerprint.hex())
1832 } else {
1833 chat::SyncId::ContactAddr(contact.addr.clone())
1834 };
1835
1836 chat::sync(context, sync_id, action)
1837 .await
1838 .log_err(context)
1839 .ok();
1840 }
1841 }
1842
1843 chatlist_events::emit_chatlist_changed(context);
1844 Ok(())
1845}
1846
1847pub(crate) async fn set_profile_image(
1854 context: &Context,
1855 contact_id: ContactId,
1856 profile_image: &AvatarAction,
1857) -> Result<()> {
1858 let mut contact = Contact::get_by_id(context, contact_id).await?;
1859 let changed = match profile_image {
1860 AvatarAction::Change(profile_image) => {
1861 if contact_id == ContactId::SELF {
1862 context
1863 .set_config_ex(Nosync, Config::Selfavatar, Some(profile_image))
1864 .await?;
1865 } else {
1866 contact.param.set(Param::ProfileImage, profile_image);
1867 }
1868 true
1869 }
1870 AvatarAction::Delete => {
1871 if contact_id == ContactId::SELF {
1872 context
1873 .set_config_ex(Nosync, Config::Selfavatar, None)
1874 .await?;
1875 } else {
1876 contact.param.remove(Param::ProfileImage);
1877 }
1878 true
1879 }
1880 };
1881 if changed {
1882 contact.update_param(context).await?;
1883 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1884 chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await;
1885 }
1886 Ok(())
1887}
1888
1889pub(crate) async fn set_status(
1893 context: &Context,
1894 contact_id: ContactId,
1895 status: String,
1896) -> Result<()> {
1897 if contact_id == ContactId::SELF {
1898 context
1899 .set_config_ex(Nosync, Config::Selfstatus, Some(&status))
1900 .await?;
1901 } else {
1902 let mut contact = Contact::get_by_id(context, contact_id).await?;
1903
1904 if contact.status != status {
1905 contact.status = status;
1906 contact.update_status(context).await?;
1907 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1908 }
1909 }
1910 Ok(())
1911}
1912
1913#[expect(clippy::arithmetic_side_effects)]
1915pub(crate) async fn update_last_seen(
1916 context: &Context,
1917 contact_id: ContactId,
1918 timestamp: i64,
1919) -> Result<()> {
1920 ensure!(
1921 !contact_id.is_special(),
1922 "Can not update special contact last seen timestamp"
1923 );
1924
1925 if context
1926 .sql
1927 .execute(
1928 "UPDATE contacts SET last_seen = ?1 WHERE last_seen < ?1 AND id = ?2",
1929 (timestamp, contact_id),
1930 )
1931 .await?
1932 > 0
1933 && timestamp > time() - SEEN_RECENTLY_SECONDS
1934 {
1935 context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1936 context
1937 .scheduler
1938 .interrupt_recently_seen(contact_id, timestamp)
1939 .await;
1940 }
1941 Ok(())
1942}
1943
1944pub(crate) async fn mark_contact_id_as_verified(
1948 context: &Context,
1949 contact_id: ContactId,
1950 verifier_id: Option<ContactId>,
1951) -> Result<()> {
1952 ensure_and_debug_assert_ne!(contact_id, ContactId::SELF,);
1953 ensure_and_debug_assert_ne!(
1954 Some(contact_id),
1955 verifier_id,
1956 "Contact cannot be verified by self",
1957 );
1958 let by_self = verifier_id == Some(ContactId::SELF);
1959 let mut verifier_id = verifier_id.unwrap_or(contact_id);
1960 context
1961 .sql
1962 .transaction(|transaction| {
1963 let contact_fingerprint: String = transaction.query_row(
1964 "SELECT fingerprint FROM contacts WHERE id=?",
1965 (contact_id,),
1966 |row| row.get(0),
1967 )?;
1968 if contact_fingerprint.is_empty() {
1969 bail!("Non-key-contact {contact_id} cannot be verified");
1970 }
1971 if verifier_id != ContactId::SELF {
1972 let (verifier_fingerprint, verifier_verifier_id): (String, ContactId) = transaction
1973 .query_row(
1974 "SELECT fingerprint, verifier FROM contacts WHERE id=?",
1975 (verifier_id,),
1976 |row| Ok((row.get(0)?, row.get(1)?)),
1977 )?;
1978 if verifier_fingerprint.is_empty() {
1979 bail!(
1980 "Contact {contact_id} cannot be verified by non-key-contact {verifier_id}"
1981 );
1982 }
1983 ensure!(
1984 verifier_id == contact_id || verifier_verifier_id != ContactId::UNDEFINED,
1985 "Contact {contact_id} cannot be verified by unverified contact {verifier_id}",
1986 );
1987 if verifier_verifier_id == verifier_id {
1988 verifier_id = contact_id;
1993 }
1994 }
1995 transaction.execute(
1996 "UPDATE contacts SET verifier=?1
1997 WHERE id=?2 AND (verifier=0 OR verifier=id OR ?3)",
1998 (verifier_id, contact_id, by_self),
1999 )?;
2000 Ok(())
2001 })
2002 .await?;
2003 Ok(())
2004}
2005
2006#[expect(clippy::arithmetic_side_effects)]
2007fn cat_fingerprint(ret: &mut String, name: &str, addr: &str, fingerprint: &str) {
2008 *ret += &format!("\n\n{name} ({addr}):\n{fingerprint}");
2009}
2010
2011fn split_address_book(book: &str) -> Vec<(&str, &str)> {
2012 book.lines()
2013 .collect::<Vec<&str>>()
2014 .chunks(2)
2015 .filter_map(|chunk| {
2016 let name = chunk.first()?;
2017 let addr = chunk.get(1)?;
2018 Some((*name, *addr))
2019 })
2020 .collect()
2021}
2022
2023#[derive(Debug)]
2024pub(crate) struct RecentlySeenInterrupt {
2025 contact_id: ContactId,
2026 timestamp: i64,
2027}
2028
2029#[derive(Debug)]
2030pub(crate) struct RecentlySeenLoop {
2031 handle: task::JoinHandle<()>,
2033
2034 interrupt_send: Sender<RecentlySeenInterrupt>,
2035}
2036
2037impl RecentlySeenLoop {
2038 pub(crate) fn new(context: Context) -> Self {
2039 let (interrupt_send, interrupt_recv) = channel::bounded(1);
2040
2041 let handle = task::spawn(Self::run(context, interrupt_recv));
2042 Self {
2043 handle,
2044 interrupt_send,
2045 }
2046 }
2047
2048 #[expect(clippy::arithmetic_side_effects)]
2049 async fn run(context: Context, interrupt: Receiver<RecentlySeenInterrupt>) {
2050 type MyHeapElem = (Reverse<i64>, ContactId);
2051
2052 let now = SystemTime::now();
2053 let now_ts = now
2054 .duration_since(SystemTime::UNIX_EPOCH)
2055 .unwrap_or_default()
2056 .as_secs() as i64;
2057
2058 let mut unseen_queue: BinaryHeap<MyHeapElem> = context
2064 .sql
2065 .query_map_collect(
2066 "SELECT id, last_seen FROM contacts
2067 WHERE last_seen > ?",
2068 (now_ts - SEEN_RECENTLY_SECONDS,),
2069 |row| {
2070 let contact_id: ContactId = row.get("id")?;
2071 let last_seen: i64 = row.get("last_seen")?;
2072 Ok((Reverse(last_seen + SEEN_RECENTLY_SECONDS), contact_id))
2073 },
2074 )
2075 .await
2076 .unwrap_or_default();
2077
2078 loop {
2079 let now = SystemTime::now();
2080 let (until, contact_id) =
2081 if let Some((Reverse(timestamp), contact_id)) = unseen_queue.peek() {
2082 (
2083 UNIX_EPOCH
2084 + Duration::from_secs((*timestamp).try_into().unwrap_or(u64::MAX))
2085 + Duration::from_secs(1),
2086 Some(contact_id),
2087 )
2088 } else {
2089 (now + Duration::from_secs(86400), None)
2091 };
2092
2093 if let Ok(duration) = until.duration_since(now) {
2094 info!(
2095 context,
2096 "Recently seen loop waiting for {} or interrupt",
2097 duration_to_str(duration)
2098 );
2099
2100 match timeout(duration, interrupt.recv()).await {
2101 Err(_) => {
2102 if let Some(contact_id) = contact_id {
2104 context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2105 chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2106 &context,
2107 *contact_id,
2108 )
2109 .await;
2110 unseen_queue.pop();
2111 }
2112 }
2113 Ok(Err(err)) => {
2114 warn!(
2115 context,
2116 "Error receiving an interruption in recently seen loop: {}", err
2117 );
2118 return;
2121 }
2122 Ok(Ok(RecentlySeenInterrupt {
2123 contact_id,
2124 timestamp,
2125 })) => {
2126 if contact_id != ContactId::UNDEFINED {
2128 unseen_queue
2129 .push((Reverse(timestamp + SEEN_RECENTLY_SECONDS), contact_id));
2130 }
2131 }
2132 }
2133 } else {
2134 info!(
2135 context,
2136 "Recently seen loop is not waiting, event is already due."
2137 );
2138
2139 if let Some(contact_id) = contact_id {
2141 context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2142 chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2143 &context,
2144 *contact_id,
2145 )
2146 .await;
2147 }
2148 unseen_queue.pop();
2149 }
2150 }
2151 }
2152
2153 pub(crate) fn try_interrupt(&self, contact_id: ContactId, timestamp: i64) {
2154 self.interrupt_send
2155 .try_send(RecentlySeenInterrupt {
2156 contact_id,
2157 timestamp,
2158 })
2159 .ok();
2160 }
2161
2162 #[cfg(test)]
2163 pub(crate) async fn interrupt(&self, contact_id: ContactId, timestamp: i64) {
2164 self.interrupt_send
2165 .send(RecentlySeenInterrupt {
2166 contact_id,
2167 timestamp,
2168 })
2169 .await
2170 .unwrap();
2171 }
2172
2173 pub(crate) async fn abort(self) {
2174 self.handle.abort();
2175
2176 self.handle.await.ok();
2180 }
2181}
2182
2183#[cfg(test)]
2184mod contact_tests;