deltachat/
contact.rs

1//! Contacts module
2
3use 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
43/// Time during which a contact is considered as seen recently.
44const SEEN_RECENTLY_SECONDS: i64 = 600;
45
46/// Contact ID, including reserved IDs.
47///
48/// Some contact IDs are reserved to identify special contacts.  This
49/// type can represent both the special as well as normal contacts.
50#[derive(
51    Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
52)]
53pub struct ContactId(u32);
54
55impl ContactId {
56    /// Undefined contact. Used as a placeholder for trashed messages.
57    pub const UNDEFINED: ContactId = ContactId::new(0);
58
59    /// The owner of the account.
60    ///
61    /// The email-address is set by `set_config` using "addr".
62    pub const SELF: ContactId = ContactId::new(1);
63
64    /// ID of the contact for info messages.
65    pub const INFO: ContactId = ContactId::new(2);
66
67    /// ID of the contact for device messages.
68    pub const DEVICE: ContactId = ContactId::new(5);
69    pub(crate) const LAST_SPECIAL: ContactId = ContactId::new(9);
70
71    /// Address to go with [`ContactId::DEVICE`].
72    ///
73    /// This is used by APIs which need to return an email address for this contact.
74    pub const DEVICE_ADDR: &'static str = "device@localhost";
75
76    /// Creates a new [`ContactId`].
77    pub const fn new(id: u32) -> ContactId {
78        ContactId(id)
79    }
80
81    /// Whether this is a special [`ContactId`].
82    ///
83    /// Some [`ContactId`]s are reserved for special contacts like [`ContactId::SELF`],
84    /// [`ContactId::INFO`] and [`ContactId::DEVICE`].  This function indicates whether this
85    /// [`ContactId`] is any of the reserved special [`ContactId`]s (`true`) or whether it
86    /// is the [`ContactId`] of a real contact (`false`).
87    pub fn is_special(&self) -> bool {
88        self.0 <= Self::LAST_SPECIAL.0
89    }
90
91    /// Numerical representation of the [`ContactId`].
92    ///
93    /// Each contact ID has a unique numerical representation which is used in the database
94    /// (via [`rusqlite::ToSql`]) and also for FFI purposes.  In Rust code you should never
95    /// need to use this directly.
96    pub const fn to_u32(&self) -> u32 {
97        self.0
98    }
99
100    /// Sets display name for existing contact.
101    ///
102    /// Display name may be an empty string,
103    /// in which case the name displayed in the UI
104    /// for this contact will switch to the
105    /// contact's authorized name.
106    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    /// Mark contact as bot.
185    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    /// Reset gossip timestamp in all chats with this contact.
194    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    /// Updates the origin of the contacts, but only if `origin` is higher than the current one.
211    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    /// Returns contact address.
231    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
261/// Allow converting [`ContactId`] to an SQLite type.
262impl 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
270/// Allow converting an SQLite integer directly into [`ContactId`].
271impl 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
281/// Returns a vCard containing contacts with the given ids.
282pub 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            // Use the current time to not reveal our or contact's online time.
303            timestamp: Ok(now),
304        });
305    }
306
307    // XXX: newline at the end of vCard is trimmed
308    // for compatibility with core <=1.155.3
309    // Newer core should be able to deal with
310    // trailing CRLF as the fix
311    // <https://github.com/deltachat/deltachat-core-rust/pull/6522>
312    // is merged.
313    Ok(contact_tools::make_vcard(&vcard_contacts)
314        .trim_end()
315        .to_string())
316}
317
318/// Imports public key into the public key store.
319///
320/// They key may come from Autocrypt header,
321/// Autocrypt-Gossip header or a vCard.
322///
323/// If the key with the same fingerprint already exists,
324/// it is updated by merging the new key.
325pub(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
379/// Imports contacts from the given vCard.
380///
381/// Returns the ids of successfully processed contacts in the order they appear in `vcard`,
382/// regardless of whether they are just created, modified or left untouched.
383pub 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    // Importing a vCard is also an explicit user action like creating a chat with the contact. We
402    // mustn't use `Origin::AddressBook` here because the vCard may be created not by us, also we
403    // want `contact.authname` to be saved as the authname and not a locally given name.
404    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/// An object representing a single contact in memory.
473///
474/// The contact object is not updated.
475/// If you want an update, you have to recreate the object.
476///
477/// The library makes sure
478/// only to use names _authorized_ by the contact in `To:` or `Cc:`.
479/// *Given-names* as "Daddy" or "Honey" are not used there.
480/// For this purpose, internally, two names are tracked -
481/// authorized name and given name.
482/// By default, these names are equal, but functions working with contact names
483/// only affect the given name.
484#[derive(Debug)]
485pub struct Contact {
486    /// The contact ID.
487    pub id: ContactId,
488
489    /// Contact name. It is recommended to use `Contact::get_name`,
490    /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
491    /// May be empty, initially set to `authname`.
492    name: String,
493
494    /// Name authorized by the contact himself. Only this name may be spread to others,
495    /// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
496    /// to access this field.
497    authname: String,
498
499    /// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr` to access this field.
500    addr: String,
501
502    /// OpenPGP key fingerprint.
503    /// Non-empty iff the contact is a key-contact,
504    /// identified by this fingerprint.
505    fingerprint: Option<String>,
506
507    /// Blocked state. Use contact_is_blocked to access this field.
508    pub blocked: bool,
509
510    /// Time when the contact was seen last time, Unix time in seconds.
511    last_seen: i64,
512
513    /// The origin/source of the contact.
514    pub origin: Origin,
515
516    /// Parameters as Param::ProfileImage
517    pub param: Params,
518
519    /// Last seen message signature for this contact, to be displayed in the profile.
520    status: String,
521
522    /// If the contact is a bot.
523    is_bot: bool,
524}
525
526/// Possible origins of a contact.
527#[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    /// Unknown origin. Can be used as a minimum origin to specify that the caller does not care
544    /// about origin of the contact.
545    #[default]
546    Unknown = 0,
547
548    /// The contact is a mailing list address, needed to unblock mailing lists
549    MailinglistAddress = 0x2,
550
551    /// Hidden on purpose, e.g. addresses with the word "noreply" in it
552    Hidden = 0x8,
553
554    /// From: of incoming messages of unknown sender
555    IncomingUnknownFrom = 0x10,
556
557    /// Cc: of incoming messages of unknown sender
558    IncomingUnknownCc = 0x20,
559
560    /// To: of incoming messages of unknown sender
561    IncomingUnknownTo = 0x40,
562
563    /// Address scanned but not verified.
564    UnhandledQrScan = 0x80,
565
566    /// Address scanned from a SecureJoin QR code, but not verified yet.
567    UnhandledSecurejoinQrScan = 0x81,
568
569    /// Reply-To: of incoming message of known sender
570    /// Contacts with at least this origin value are shown in the contact list.
571    IncomingReplyTo = 0x100,
572
573    /// Cc: of incoming message of known sender
574    IncomingCc = 0x200,
575
576    /// additional To:'s of incoming message of known sender
577    IncomingTo = 0x400,
578
579    /// a chat was manually created for this user, but no message yet sent
580    CreateChat = 0x800,
581
582    /// message sent by us
583    OutgoingBcc = 0x1000,
584
585    /// message sent by us
586    OutgoingCc = 0x2000,
587
588    /// message sent by us
589    OutgoingTo = 0x4000,
590
591    /// internal use
592    Internal = 0x40000,
593
594    /// address is in our address book
595    AddressBook = 0x80000,
596
597    /// set on Alice's side for contacts like Bob that have scanned the QR code offered by her. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling contact_is_verified() !
598    SecurejoinInvited = 0x0100_0000,
599
600    /// Set on Bob's side for contacts scanned from a QR code.
601    /// Only means the contact has been scanned from the QR code,
602    /// but does not mean that securejoin succeeded
603    /// or the key has not changed since the last scan.
604    /// Getting the current key verification status requires calling contact_is_verified() !
605    SecurejoinJoined = 0x0200_0000,
606
607    /// contact added manually by create_contact(), this should be the largest origin as otherwise the user cannot modify the names
608    ManuallyCreated = 0x0400_0000,
609}
610
611impl Origin {
612    /// Contacts that are known, i. e. they came in via accepted contacts or
613    /// themselves an accepted contact. Known contacts are shown in the
614    /// contact list when one creates a chat and wants to add members etc.
615    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    /// Loads a single contact object from the database.
629    ///
630    /// Returns an error if the contact does not exist.
631    ///
632    /// For contact ContactId::SELF (1), the function returns sth.
633    /// like "Me" in the selected language and the email address
634    /// defined by set_config().
635    ///
636    /// For contact ContactId::DEVICE, the function overrides
637    /// the contact name and status with localized address.
638    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    /// Loads a single contact object from the database.
646    ///
647    /// Similar to [`Contact::get_by_id()`] but returns `None` if the contact does not exist.
648    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    /// Returns `true` if this contact is blocked.
719    pub fn is_blocked(&self) -> bool {
720        self.blocked
721    }
722
723    /// Returns last seen timestamp.
724    pub fn last_seen(&self) -> i64 {
725        self.last_seen
726    }
727
728    /// Returns `true` if this contact was seen recently.
729    #[expect(clippy::arithmetic_side_effects)]
730    pub fn was_seen_recently(&self) -> bool {
731        time() - self.last_seen <= SEEN_RECENTLY_SECONDS
732    }
733
734    /// Check if a contact is blocked.
735    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    /// Block the given contact.
747    pub async fn block(context: &Context, id: ContactId) -> Result<()> {
748        set_blocked(context, Sync, id, true).await
749    }
750
751    /// Unblock the given contact.
752    pub async fn unblock(context: &Context, id: ContactId) -> Result<()> {
753        set_blocked(context, Sync, id, false).await
754    }
755
756    /// Add a single contact as a result of an _explicit_ user action.
757    ///
758    /// We assume, the contact name, if any, is entered by the user and is used "as is" therefore,
759    /// normalize() is *not* called for the name. If the contact is blocked, it is unblocked.
760    ///
761    /// To add a number of contacts, see `add_address_book()` which is much faster for adding
762    /// a bunch of addresses.
763    ///
764    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
765    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    /// Mark messages from a contact as noticed.
807    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    /// Returns whether contact is a bot.
819    pub fn is_bot(&self) -> bool {
820        self.is_bot
821    }
822
823    /// Looks up a known and unblocked contact with a given e-mail address.
824    /// To get a list of all known and unblocked contacts, use contacts_get_contacts().
825    ///
826    ///
827    /// **POTENTIAL SECURITY ISSUE**: If there are multiple contacts with this address
828    /// (e.g. an address-contact and a key-contact),
829    /// this looks up the most recently seen contact,
830    /// i.e. which contact is returned depends on which contact last sent a message.
831    /// If the user just clicked on a mailto: link, then this is the best thing you can do.
832    /// But **DO NOT** internally represent contacts by their email address
833    /// and do not use this function to look them up;
834    /// otherwise this function will sometimes look up the wrong contact.
835    /// Instead, you should internally represent contacts by their ids.
836    ///
837    /// Known and unblocked contacts will be returned by `get_contacts()`.
838    ///
839    /// To validate an e-mail address independently of the contact database
840    /// use `may_be_valid_addr()`.
841    ///
842    /// Returns the contact ID of the contact belonging to the e-mail address or 0 if there is no
843    /// contact that is or was introduced by an accepted contact.
844    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    /// The same as `lookup_id_by_addr()`, but internal function. Currently also allows looking up
853    /// not unblocked contacts.
854    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    /// Lookup a contact and create it if it does not exist yet.
913    /// If `fingerprint` is non-empty, a key-contact with this fingerprint is added / looked up.
914    /// Otherwise, an address-contact with `addr` is added / looked up.
915    /// A name and an "origin" can be given.
916    ///
917    /// The "origin" is where the address comes from -
918    /// from-header, cc-header, addressbook, qr, manual-edit etc.
919    /// In general, "better" origins overwrite the names of "worse" origins -
920    /// Eg. if we got a name in cc-header and later in from-header, the name will change -
921    /// this does not happen the other way round.
922    ///
923    /// The "best" origin are manually created contacts -
924    /// names given manually can only be overwritten by further manual edits
925    /// (until they are set empty again or reset to the name seen in the From-header).
926    ///
927    /// These manually edited names are _never_ used for sending on the wire -
928    /// this should avoid sending sth. as "Mama" or "Daddy" to some 3rd party.
929    /// Instead, for the wire, we use so called "authnames"
930    /// that can only be set and updated by a From-header.
931    ///
932    /// The different names used in the function are:
933    /// - "name": name passed as function argument, belonging to the given origin
934    /// - "row_name": current name used in the database, typically set to "name"
935    /// - "row_authname": name as authorized from a contact, set only through a From-header
936    ///   Depending on the origin, both, "row_name" and "row_authname" are updated from "name".
937    ///
938    /// Returns the contact_id and a `Modifier` value indicating if a modification occurred.
939    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            // The user may accidentally have written to a "noreply" address with another MUA:
970            if addr.contains("noreply")
971                || addr.contains("no-reply")
972                || addr.starts_with("notifications@")
973                // Filter out use-once addresses (like reply+AEJDGPOECLAP...@reply.github.com):
974                || (addr.len() > 50 && addr.contains('+'))
975            {
976                info!(context, "hiding contact {}", addr);
977                origin = Origin::Hidden;
978                // For these kind of email addresses, sender and address often don't belong together
979                // (like hocuri <notifications@github.com>). In this example, hocuri shouldn't
980                // be saved as the displayname for notifications@github.com.
981                name = "".to_string();
982            }
983        }
984
985        // If the origin indicates that user entered the contact manually, from the address book or
986        // from the QR-code scan (potentially from the address book of their other phone), then name
987        // should go into the "name" column and never into "authname" column, to avoid leaking it
988        // into the network.
989        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    /// Add a number of contacts.
1112    ///
1113    /// Typically used to add the whole address book from the OS. As names here are typically not
1114    /// well formatted, we call `normalize()` for each name given.
1115    ///
1116    /// No email-address is added twice.
1117    /// Trying to add email-addresses that are already in the contact list,
1118    /// results in updating the name unless the name was changed manually by the user.
1119    /// If any email-address or any name is really updated,
1120    /// the event `DC_EVENT_CONTACTS_CHANGED` is sent.
1121    ///
1122    /// To add a single contact entered by the user, you should prefer `Contact::create`,
1123    /// however, for adding a bunch of addresses, this function is much faster.
1124    ///
1125    /// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
1126    ///
1127    /// Returns the number of modified contacts.
1128    #[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    /// Returns known and unblocked contacts.
1163    ///
1164    /// To get information about a single contact, see get_contact().
1165    /// By default, key-contacts are listed.
1166    ///
1167    /// * `listflags` - A combination of flags:
1168    ///   - `DC_GCL_ADD_SELF` - Add SELF unless filtered by other parameters.
1169    ///   - `DC_GCL_ADDRESS` - List address-contacts instead of key-contacts.
1170    /// * `query` - A string to filter the list.
1171    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    /// Adds blocked mailinglists and broadcast channels as pseudo-contacts
1294    /// to allow unblocking them as if they are contacts
1295    /// (this way, only one unblock-ffi is needed and only one set of ui-functions,
1296    /// from the users perspective,
1297    /// there is not much difference in an email- and a mailinglist-address)
1298    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                        // Set some fingerprint so that is_pgp_contact() returns true,
1330                        // and the contact isn't marked with a letter icon.
1331                        "Blocked_broadcast"
1332                    } else {
1333                        ""
1334                    };
1335                    // Always do an update in case the blocking is reset or name is changed.
1336                    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    /// Get blocked contacts.
1358    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    /// Returns a textual summary of the encryption state for the contact.
1378    ///
1379    /// This function returns a string explaining the encryption state
1380    /// of the contact and if the connection is encrypted the
1381    /// fingerprints of the keys involved.
1382    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    /// Delete a contact so that it disappears from the corresponding lists.
1454    /// Depending on whether there are ongoing chats, deletion is done by physical deletion or hiding.
1455    /// The contact is deleted from the local device.
1456    ///
1457    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
1458    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                // make sure, the transaction starts with a write command and becomes EXCLUSIVE by that -
1465                // upgrading later may be impossible by races.
1466                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    /// Updates `param` column in the database.
1486    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    /// Updates `status` column in the database.
1498    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    /// Get the ID of the contact.
1510    pub fn get_id(&self) -> ContactId {
1511        self.id
1512    }
1513
1514    /// Get email address. The email address is always set for a contact.
1515    pub fn get_addr(&self) -> &str {
1516        &self.addr
1517    }
1518
1519    /// Returns true if the contact is a key-contact.
1520    /// Otherwise it is an addresss-contact.
1521    pub fn is_key_contact(&self) -> bool {
1522        self.fingerprint.is_some() || self.id == ContactId::SELF
1523    }
1524
1525    /// Returns OpenPGP fingerprint of a contact.
1526    ///
1527    /// `None` for address-contacts.
1528    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    /// Returns OpenPGP public key of a contact.
1537    ///
1538    /// Returns `None` if the contact is not a key-contact
1539    /// or if the key is not available.
1540    /// It is possible for a key-contact to not have a key,
1541    /// e.g. if only the fingerprint is known from a QR-code.
1542    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    /// Get name authorized by the contact.
1573    pub fn get_authname(&self) -> &str {
1574        &self.authname
1575    }
1576
1577    /// Get the contact name. This is the name as modified by the local user.
1578    /// May be an empty string.
1579    ///
1580    /// This name is typically used in a form where the user can edit the name of a contact.
1581    /// To get a fine name to display in lists etc., use `Contact::get_display_name` or `Contact::get_name_n_addr`.
1582    pub fn get_name(&self) -> &str {
1583        &self.name
1584    }
1585
1586    /// Get display name. This is the name as defined by the contact himself,
1587    /// modified by the user or, if both are unset, the email address.
1588    ///
1589    /// This name is typically used in lists.
1590    /// To get the name editable in a formular, use `Contact::get_name`.
1591    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    /// Get a summary of name and address.
1602    ///
1603    /// The returned string is either "Name (email@domain.com)" or just
1604    /// "email@domain.com" if the name is unset.
1605    ///
1606    /// The result should only be used locally and never sent over the network
1607    /// as it leaks the local contact name.
1608    ///
1609    /// The summary is typically used when asking the user something about the contact.
1610    /// The attached email address makes the question unique, eg. "Chat with Alan Miller (am@uniquedomain.com)?"
1611    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    /// Get the contact's profile image.
1622    /// This is the image set by each remote user on their own
1623    /// using set_config(context, "selfavatar", image).
1624    pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1625        self.get_profile_image_ex(context, true).await
1626    }
1627
1628    /// Get the contact's profile image.
1629    /// This is the image set by each remote user on their own
1630    /// using set_config(context, "selfavatar", image).
1631    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))); // get_config() calls get_abs_path() internally already
1639            }
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    /// Returns a color for the contact.
1655    /// For self-contact this returns gray if own keypair doesn't exist yet.
1656    /// See also [`self::get_color`].
1657    pub fn get_color(&self) -> u32 {
1658        get_color(self.id == ContactId::SELF, &self.addr, &self.fingerprint())
1659    }
1660
1661    /// Returns a color for the contact.
1662    /// Ensures that the color isn't gray. For self-contact this generates own keypair if it doesn't
1663    /// exist yet.
1664    /// See also [`self::get_color`].
1665    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    /// Gets the contact's status.
1674    ///
1675    /// Status is the last signature received in a message from this contact.
1676    pub fn get_status(&self) -> &str {
1677        self.status.as_str()
1678    }
1679
1680    /// Returns whether end-to-end encryption to the contact is available.
1681    pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1682        if self.id == ContactId::SELF {
1683            // We don't need to check if we have our own key.
1684            return Ok(true);
1685        }
1686        Ok(self.public_key(context).await?.is_some())
1687    }
1688
1689    /// Returns true if the contact
1690    /// can be added to verified chats.
1691    ///
1692    /// If contact is verified
1693    /// UI should display green checkmark after the contact name
1694    /// in contact list items and
1695    /// in chat member list items.
1696    ///
1697    /// In contact profile view, use this function only if there is no chat with the contact,
1698    /// otherwise use is_chat_protected().
1699    /// Use [Self::get_verifier_id] to display the verifier contact
1700    /// in the info section of the contact profile.
1701    pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1702        // We're always sort of secured-verified as we could verify the key on this device any time with the key
1703        // on this device
1704        if self.id == ContactId::SELF {
1705            return Ok(true);
1706        }
1707
1708        Ok(self.get_verifier_id(context).await?.is_some())
1709    }
1710
1711    /// Returns the `ContactId` that verified the contact.
1712    ///
1713    /// If this returns Some(_),
1714    /// display green checkmark in the profile and "Introduced by ..." line
1715    /// with the name of the contact.
1716    ///
1717    /// If this returns `Some(None)`, then the contact is verified,
1718    /// but it's unclear by whom.
1719    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    /// Returns the number of real (i.e. non-special) contacts in the database.
1736    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    /// Returns true if a contact with this ID exists.
1752    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
1765/// Returns a color for a contact having given attributes.
1766///
1767/// The color is calculated from contact's fingerprint (for key-contacts) or email address (for
1768/// address-contacts; should be lowercased to avoid allocation) and can be used for an fallback
1769/// avatar with white initials as well as for headlines in bubbles of group chats.
1770pub 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
1780// Updates the names of the chats which use the contact name.
1781//
1782// This is one of the few duplicated data, however, getting the chat list is easier this way.
1783fn 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            // Chat name updated
1826            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        // also (un)block all chats with _only_ this contact - we do not delete them to allow a
1856        // non-destructive blocking->unblocking.
1857        // (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
1858        // However, I'm not sure about this point; it may be confusing if the user wants to add other people;
1859        // this would result in recreating the same group...)
1860        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        // also unblock mailinglist
1880        // if the contact is a mailinglist address explicitly created to allow unblocking
1881        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
1910/// Set profile image for a contact.
1911///
1912/// The given profile image is expected to be already in the blob directory
1913/// as profile images can be set only by receiving messages, this should be always the case, however.
1914///
1915/// For contact SELF, the image is not saved in the contact-database but as Config::Selfavatar.
1916pub(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
1952/// Sets contact status.
1953///
1954/// For contact SELF, the status is not saved in the contact table, but as Config::Selfstatus.
1955pub(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/// Updates last seen timestamp of the contact if it is earlier than the given `timestamp`.
1977#[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
2007/// Marks contact `contact_id` as verified by `verifier_id`.
2008///
2009/// `verifier_id == None` means that the verifier is unknown.
2010pub(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                    // Avoid introducing incorrect reverse chains: if the verifier itself has an
2052                    // unknown verifier, it may be `contact_id` actually (directly or indirectly) on
2053                    // the other device (which is needed for getting "verified by unknown contact"
2054                    // in the first place).
2055                    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    /// Task running "recently seen" loop.
2095    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        // Priority contains all recently seen sorted by the timestamp
2122        // when they become not recently seen.
2123        //
2124        // Initialize with contacts which are currently seen, but will
2125        // become unseen in the future.
2126        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                    // Sleep for 24 hours.
2153                    (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                        // Timeout, notify about contact.
2166                        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                        // Maybe the sender side is closed.
2182                        // Terminate the loop to avoid looping indefinitely.
2183                        return;
2184                    }
2185                    Ok(Ok(RecentlySeenInterrupt {
2186                        contact_id,
2187                        timestamp,
2188                    })) => {
2189                        // Received an interrupt.
2190                        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                // Event is already in the past.
2203                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        // Await aborted task to ensure the `Future` is dropped
2240        // with all resources moved inside such as the `Context`
2241        // reference to `InnerContext`.
2242        self.handle.await.ok();
2243    }
2244}
2245
2246#[cfg(test)]
2247mod contact_tests;