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::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
42/// Time during which a contact is considered as seen recently.
43const SEEN_RECENTLY_SECONDS: i64 = 600;
44
45/// Contact ID, including reserved IDs.
46///
47/// Some contact IDs are reserved to identify special contacts.  This
48/// type can represent both the special as well as normal contacts.
49#[derive(
50    Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
51)]
52pub struct ContactId(u32);
53
54impl ContactId {
55    /// Undefined contact. Used as a placeholder for trashed messages.
56    pub const UNDEFINED: ContactId = ContactId::new(0);
57
58    /// The owner of the account.
59    ///
60    /// The email-address is set by `set_config` using "addr".
61    pub const SELF: ContactId = ContactId::new(1);
62
63    /// ID of the contact for info messages.
64    pub const INFO: ContactId = ContactId::new(2);
65
66    /// ID of the contact for device messages.
67    pub const DEVICE: ContactId = ContactId::new(5);
68    pub(crate) const LAST_SPECIAL: ContactId = ContactId::new(9);
69
70    /// Address to go with [`ContactId::DEVICE`].
71    ///
72    /// This is used by APIs which need to return an email address for this contact.
73    pub const DEVICE_ADDR: &'static str = "device@localhost";
74
75    /// Creates a new [`ContactId`].
76    pub const fn new(id: u32) -> ContactId {
77        ContactId(id)
78    }
79
80    /// Whether this is a special [`ContactId`].
81    ///
82    /// Some [`ContactId`]s are reserved for special contacts like [`ContactId::SELF`],
83    /// [`ContactId::INFO`] and [`ContactId::DEVICE`].  This function indicates whether this
84    /// [`ContactId`] is any of the reserved special [`ContactId`]s (`true`) or whether it
85    /// is the [`ContactId`] of a real contact (`false`).
86    pub fn is_special(&self) -> bool {
87        self.0 <= Self::LAST_SPECIAL.0
88    }
89
90    /// Numerical representation of the [`ContactId`].
91    ///
92    /// Each contact ID has a unique numerical representation which is used in the database
93    /// (via [`rusqlite::ToSql`]) and also for FFI purposes.  In Rust code you should never
94    /// need to use this directly.
95    pub const fn to_u32(&self) -> u32 {
96        self.0
97    }
98
99    /// Sets display name for existing contact.
100    ///
101    /// Display name may be an empty string,
102    /// in which case the name displayed in the UI
103    /// for this contact will switch to the
104    /// contact's authorized name.
105    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    /// Mark contact as bot.
184    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    /// Reset gossip timestamp in all chats with this contact.
193    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    /// Updates the origin of the contacts, but only if `origin` is higher than the current one.
210    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    /// Returns contact address.
230    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
260/// Allow converting [`ContactId`] to an SQLite type.
261impl 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
269/// Allow converting an SQLite integer directly into [`ContactId`].
270impl 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
280/// Returns a vCard containing contacts with the given ids.
281pub 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            // Use the current time to not reveal our or contact's online time.
302            timestamp: Ok(now),
303        });
304    }
305
306    // XXX: newline at the end of vCard is trimmed
307    // for compatibility with core <=1.155.3
308    // Newer core should be able to deal with
309    // trailing CRLF as the fix
310    // <https://github.com/deltachat/deltachat-core-rust/pull/6522>
311    // is merged.
312    Ok(contact_tools::make_vcard(&vcard_contacts)
313        .trim_end()
314        .to_string())
315}
316
317/// Imports contacts from the given vCard.
318///
319/// Returns the ids of successfully processed contacts in the order they appear in `vcard`,
320/// regardless of whether they are just created, modified or left untouched.
321pub 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    // Importing a vCard is also an explicit user action like creating a chat with the contact. We
340    // mustn't use `Origin::AddressBook` here because the vCard may be created not by us, also we
341    // want `contact.authname` to be saved as the authname and not a locally given name.
342    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/// An object representing a single contact in memory.
420///
421/// The contact object is not updated.
422/// If you want an update, you have to recreate the object.
423///
424/// The library makes sure
425/// only to use names _authorized_ by the contact in `To:` or `Cc:`.
426/// *Given-names* as "Daddy" or "Honey" are not used there.
427/// For this purpose, internally, two names are tracked -
428/// authorized name and given name.
429/// By default, these names are equal, but functions working with contact names
430/// only affect the given name.
431#[derive(Debug)]
432pub struct Contact {
433    /// The contact ID.
434    pub id: ContactId,
435
436    /// Contact name. It is recommended to use `Contact::get_name`,
437    /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
438    /// May be empty, initially set to `authname`.
439    name: String,
440
441    /// Name authorized by the contact himself. Only this name may be spread to others,
442    /// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
443    /// to access this field.
444    authname: String,
445
446    /// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr` to access this field.
447    addr: String,
448
449    /// OpenPGP key fingerprint.
450    /// Non-empty iff the contact is a key-contact,
451    /// identified by this fingerprint.
452    fingerprint: Option<String>,
453
454    /// Blocked state. Use contact_is_blocked to access this field.
455    pub blocked: bool,
456
457    /// Time when the contact was seen last time, Unix time in seconds.
458    last_seen: i64,
459
460    /// The origin/source of the contact.
461    pub origin: Origin,
462
463    /// Parameters as Param::ProfileImage
464    pub param: Params,
465
466    /// Last seen message signature for this contact, to be displayed in the profile.
467    status: String,
468
469    /// If the contact is a bot.
470    is_bot: bool,
471}
472
473/// Possible origins of a contact.
474#[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    /// Unknown origin. Can be used as a minimum origin to specify that the caller does not care
491    /// about origin of the contact.
492    #[default]
493    Unknown = 0,
494
495    /// The contact is a mailing list address, needed to unblock mailing lists
496    MailinglistAddress = 0x2,
497
498    /// Hidden on purpose, e.g. addresses with the word "noreply" in it
499    Hidden = 0x8,
500
501    /// From: of incoming messages of unknown sender
502    IncomingUnknownFrom = 0x10,
503
504    /// Cc: of incoming messages of unknown sender
505    IncomingUnknownCc = 0x20,
506
507    /// To: of incoming messages of unknown sender
508    IncomingUnknownTo = 0x40,
509
510    /// Address scanned but not verified.
511    UnhandledQrScan = 0x80,
512
513    /// Address scanned from a SecureJoin QR code, but not verified yet.
514    UnhandledSecurejoinQrScan = 0x81,
515
516    /// Reply-To: of incoming message of known sender
517    /// Contacts with at least this origin value are shown in the contact list.
518    IncomingReplyTo = 0x100,
519
520    /// Cc: of incoming message of known sender
521    IncomingCc = 0x200,
522
523    /// additional To:'s of incoming message of known sender
524    IncomingTo = 0x400,
525
526    /// a chat was manually created for this user, but no message yet sent
527    CreateChat = 0x800,
528
529    /// message sent by us
530    OutgoingBcc = 0x1000,
531
532    /// message sent by us
533    OutgoingCc = 0x2000,
534
535    /// message sent by us
536    OutgoingTo = 0x4000,
537
538    /// internal use
539    Internal = 0x40000,
540
541    /// address is in our address book
542    AddressBook = 0x80000,
543
544    /// 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() !
545    SecurejoinInvited = 0x0100_0000,
546
547    /// Set on Bob's side for contacts scanned from a QR code.
548    /// Only means the contact has been scanned from the QR code,
549    /// but does not mean that securejoin succeeded
550    /// or the key has not changed since the last scan.
551    /// Getting the current key verification status requires calling contact_is_verified() !
552    SecurejoinJoined = 0x0200_0000,
553
554    /// contact added manually by create_contact(), this should be the largest origin as otherwise the user cannot modify the names
555    ManuallyCreated = 0x0400_0000,
556}
557
558impl Origin {
559    /// Contacts that are known, i. e. they came in via accepted contacts or
560    /// themselves an accepted contact. Known contacts are shown in the
561    /// contact list when one creates a chat and wants to add members etc.
562    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    /// Loads a single contact object from the database.
576    ///
577    /// Returns an error if the contact does not exist.
578    ///
579    /// For contact ContactId::SELF (1), the function returns sth.
580    /// like "Me" in the selected language and the email address
581    /// defined by set_config().
582    ///
583    /// For contact ContactId::DEVICE, the function overrides
584    /// the contact name and status with localized address.
585    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    /// Loads a single contact object from the database.
593    ///
594    /// Similar to [`Contact::get_by_id()`] but returns `None` if the contact does not exist.
595    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    /// Returns `true` if this contact is blocked.
666    pub fn is_blocked(&self) -> bool {
667        self.blocked
668    }
669
670    /// Returns last seen timestamp.
671    pub fn last_seen(&self) -> i64 {
672        self.last_seen
673    }
674
675    /// Returns `true` if this contact was seen recently.
676    #[expect(clippy::arithmetic_side_effects)]
677    pub fn was_seen_recently(&self) -> bool {
678        time() - self.last_seen <= SEEN_RECENTLY_SECONDS
679    }
680
681    /// Check if a contact is blocked.
682    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    /// Block the given contact.
694    pub async fn block(context: &Context, id: ContactId) -> Result<()> {
695        set_blocked(context, Sync, id, true).await
696    }
697
698    /// Unblock the given contact.
699    pub async fn unblock(context: &Context, id: ContactId) -> Result<()> {
700        set_blocked(context, Sync, id, false).await
701    }
702
703    /// Add a single contact as a result of an _explicit_ user action.
704    ///
705    /// We assume, the contact name, if any, is entered by the user and is used "as is" therefore,
706    /// normalize() is *not* called for the name. If the contact is blocked, it is unblocked.
707    ///
708    /// To add a number of contacts, see `add_address_book()` which is much faster for adding
709    /// a bunch of addresses.
710    ///
711    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
712    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    /// Mark messages from a contact as noticed.
754    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    /// Returns whether contact is a bot.
766    pub fn is_bot(&self) -> bool {
767        self.is_bot
768    }
769
770    /// Looks up a known and unblocked contact with a given e-mail address.
771    /// To get a list of all known and unblocked contacts, use contacts_get_contacts().
772    ///
773    ///
774    /// **POTENTIAL SECURITY ISSUE**: If there are multiple contacts with this address
775    /// (e.g. an address-contact and a key-contact),
776    /// this looks up the most recently seen contact,
777    /// i.e. which contact is returned depends on which contact last sent a message.
778    /// If the user just clicked on a mailto: link, then this is the best thing you can do.
779    /// But **DO NOT** internally represent contacts by their email address
780    /// and do not use this function to look them up;
781    /// otherwise this function will sometimes look up the wrong contact.
782    /// Instead, you should internally represent contacts by their ids.
783    ///
784    /// Known and unblocked contacts will be returned by `get_contacts()`.
785    ///
786    /// To validate an e-mail address independently of the contact database
787    /// use `may_be_valid_addr()`.
788    ///
789    /// Returns the contact ID of the contact belonging to the e-mail address or 0 if there is no
790    /// contact that is or was introduced by an accepted contact.
791    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    /// The same as `lookup_id_by_addr()`, but internal function. Currently also allows looking up
800    /// not unblocked contacts.
801    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    /// Lookup a contact and create it if it does not exist yet.
860    /// If `fingerprint` is non-empty, a key-contact with this fingerprint is added / looked up.
861    /// Otherwise, an address-contact with `addr` is added / looked up.
862    /// A name and an "origin" can be given.
863    ///
864    /// The "origin" is where the address comes from -
865    /// from-header, cc-header, addressbook, qr, manual-edit etc.
866    /// In general, "better" origins overwrite the names of "worse" origins -
867    /// Eg. if we got a name in cc-header and later in from-header, the name will change -
868    /// this does not happen the other way round.
869    ///
870    /// The "best" origin are manually created contacts -
871    /// names given manually can only be overwritten by further manual edits
872    /// (until they are set empty again or reset to the name seen in the From-header).
873    ///
874    /// These manually edited names are _never_ used for sending on the wire -
875    /// this should avoid sending sth. as "Mama" or "Daddy" to some 3rd party.
876    /// Instead, for the wire, we use so called "authnames"
877    /// that can only be set and updated by a From-header.
878    ///
879    /// The different names used in the function are:
880    /// - "name": name passed as function argument, belonging to the given origin
881    /// - "row_name": current name used in the database, typically set to "name"
882    /// - "row_authname": name as authorized from a contact, set only through a From-header
883    ///   Depending on the origin, both, "row_name" and "row_authname" are updated from "name".
884    ///
885    /// Returns the contact_id and a `Modifier` value indicating if a modification occurred.
886    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            // The user may accidentally have written to a "noreply" address with another MUA:
917            if addr.contains("noreply")
918                || addr.contains("no-reply")
919                || addr.starts_with("notifications@")
920                // Filter out use-once addresses (like reply+AEJDGPOECLAP...@reply.github.com):
921                || (addr.len() > 50 && addr.contains('+'))
922            {
923                info!(context, "hiding contact {}", addr);
924                origin = Origin::Hidden;
925                // For these kind of email addresses, sender and address often don't belong together
926                // (like hocuri <notifications@github.com>). In this example, hocuri shouldn't
927                // be saved as the displayname for notifications@github.com.
928                name = "".to_string();
929            }
930        }
931
932        // If the origin indicates that user entered the contact manually, from the address book or
933        // from the QR-code scan (potentially from the address book of their other phone), then name
934        // should go into the "name" column and never into "authname" column, to avoid leaking it
935        // into the network.
936        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    /// Add a number of contacts.
1059    ///
1060    /// Typically used to add the whole address book from the OS. As names here are typically not
1061    /// well formatted, we call `normalize()` for each name given.
1062    ///
1063    /// No email-address is added twice.
1064    /// Trying to add email-addresses that are already in the contact list,
1065    /// results in updating the name unless the name was changed manually by the user.
1066    /// If any email-address or any name is really updated,
1067    /// the event `DC_EVENT_CONTACTS_CHANGED` is sent.
1068    ///
1069    /// To add a single contact entered by the user, you should prefer `Contact::create`,
1070    /// however, for adding a bunch of addresses, this function is much faster.
1071    ///
1072    /// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
1073    ///
1074    /// Returns the number of modified contacts.
1075    #[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    /// Returns known and unblocked contacts.
1110    ///
1111    /// To get information about a single contact, see get_contact().
1112    /// By default, key-contacts are listed.
1113    ///
1114    /// * `listflags` - A combination of flags:
1115    ///   - `DC_GCL_ADD_SELF` - Add SELF unless filtered by other parameters.
1116    ///   - `DC_GCL_ADDRESS` - List address-contacts instead of key-contacts.
1117    /// * `query` - A string to filter the list.
1118    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    /// Adds blocked mailinglists and broadcast channels as pseudo-contacts
1241    /// to allow unblocking them as if they are contacts
1242    /// (this way, only one unblock-ffi is needed and only one set of ui-functions,
1243    /// from the users perspective,
1244    /// there is not much difference in an email- and a mailinglist-address)
1245    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                        // Set some fingerprint so that is_pgp_contact() returns true,
1277                        // and the contact isn't marked with a letter icon.
1278                        "Blocked_broadcast"
1279                    } else {
1280                        ""
1281                    };
1282                    // Always do an update in case the blocking is reset or name is changed.
1283                    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    /// Get blocked contacts.
1305    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    /// Returns a textual summary of the encryption state for the contact.
1325    ///
1326    /// This function returns a string explaining the encryption state
1327    /// of the contact and if the connection is encrypted the
1328    /// fingerprints of the keys involved.
1329    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    /// Delete a contact so that it disappears from the corresponding lists.
1391    /// Depending on whether there are ongoing chats, deletion is done by physical deletion or hiding.
1392    /// The contact is deleted from the local device.
1393    ///
1394    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
1395    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                // make sure, the transaction starts with a write command and becomes EXCLUSIVE by that -
1402                // upgrading later may be impossible by races.
1403                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    /// Updates `param` column in the database.
1423    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    /// Updates `status` column in the database.
1435    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    /// Get the ID of the contact.
1447    pub fn get_id(&self) -> ContactId {
1448        self.id
1449    }
1450
1451    /// Get email address. The email address is always set for a contact.
1452    pub fn get_addr(&self) -> &str {
1453        &self.addr
1454    }
1455
1456    /// Returns true if the contact is a key-contact.
1457    /// Otherwise it is an addresss-contact.
1458    pub fn is_key_contact(&self) -> bool {
1459        self.fingerprint.is_some() || self.id == ContactId::SELF
1460    }
1461
1462    /// Returns OpenPGP fingerprint of a contact.
1463    ///
1464    /// `None` for address-contacts.
1465    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    /// Returns OpenPGP public key of a contact.
1474    ///
1475    /// Returns `None` if the contact is not a key-contact
1476    /// or if the key is not available.
1477    /// It is possible for a key-contact to not have a key,
1478    /// e.g. if only the fingerprint is known from a QR-code.
1479    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    /// Get name authorized by the contact.
1510    pub fn get_authname(&self) -> &str {
1511        &self.authname
1512    }
1513
1514    /// Get the contact name. This is the name as modified by the local user.
1515    /// May be an empty string.
1516    ///
1517    /// This name is typically used in a form where the user can edit the name of a contact.
1518    /// To get a fine name to display in lists etc., use `Contact::get_display_name` or `Contact::get_name_n_addr`.
1519    pub fn get_name(&self) -> &str {
1520        &self.name
1521    }
1522
1523    /// Get display name. This is the name as defined by the contact himself,
1524    /// modified by the user or, if both are unset, the email address.
1525    ///
1526    /// This name is typically used in lists.
1527    /// To get the name editable in a formular, use `Contact::get_name`.
1528    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    /// Get a summary of name and address.
1539    ///
1540    /// The returned string is either "Name (email@domain.com)" or just
1541    /// "email@domain.com" if the name is unset.
1542    ///
1543    /// The result should only be used locally and never sent over the network
1544    /// as it leaks the local contact name.
1545    ///
1546    /// The summary is typically used when asking the user something about the contact.
1547    /// The attached email address makes the question unique, eg. "Chat with Alan Miller (am@uniquedomain.com)?"
1548    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    /// Get the contact's profile image.
1559    /// This is the image set by each remote user on their own
1560    /// using set_config(context, "selfavatar", image).
1561    pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1562        self.get_profile_image_ex(context, true).await
1563    }
1564
1565    /// Get the contact's profile image.
1566    /// This is the image set by each remote user on their own
1567    /// using set_config(context, "selfavatar", image).
1568    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))); // get_config() calls get_abs_path() internally already
1576            }
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    /// Returns a color for the contact.
1592    /// For self-contact this returns gray if own keypair doesn't exist yet.
1593    /// See also [`self::get_color`].
1594    pub fn get_color(&self) -> u32 {
1595        get_color(self.id == ContactId::SELF, &self.addr, &self.fingerprint())
1596    }
1597
1598    /// Returns a color for the contact.
1599    /// Ensures that the color isn't gray. For self-contact this generates own keypair if it doesn't
1600    /// exist yet.
1601    /// See also [`self::get_color`].
1602    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    /// Gets the contact's status.
1611    ///
1612    /// Status is the last signature received in a message from this contact.
1613    pub fn get_status(&self) -> &str {
1614        self.status.as_str()
1615    }
1616
1617    /// Returns whether end-to-end encryption to the contact is available.
1618    pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1619        if self.id == ContactId::SELF {
1620            // We don't need to check if we have our own key.
1621            return Ok(true);
1622        }
1623        Ok(self.public_key(context).await?.is_some())
1624    }
1625
1626    /// Returns true if the contact
1627    /// can be added to verified chats.
1628    ///
1629    /// If contact is verified
1630    /// UI should display green checkmark after the contact name
1631    /// in contact list items and
1632    /// in chat member list items.
1633    ///
1634    /// In contact profile view, use this function only if there is no chat with the contact,
1635    /// otherwise use is_chat_protected().
1636    /// Use [Self::get_verifier_id] to display the verifier contact
1637    /// in the info section of the contact profile.
1638    pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1639        // We're always sort of secured-verified as we could verify the key on this device any time with the key
1640        // on this device
1641        if self.id == ContactId::SELF {
1642            return Ok(true);
1643        }
1644
1645        Ok(self.get_verifier_id(context).await?.is_some())
1646    }
1647
1648    /// Returns the `ContactId` that verified the contact.
1649    ///
1650    /// If this returns Some(_),
1651    /// display green checkmark in the profile and "Introduced by ..." line
1652    /// with the name of the contact.
1653    ///
1654    /// If this returns `Some(None)`, then the contact is verified,
1655    /// but it's unclear by whom.
1656    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    /// Returns the number of real (i.e. non-special) contacts in the database.
1673    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    /// Returns true if a contact with this ID exists.
1689    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
1702/// Returns a color for a contact having given attributes.
1703///
1704/// The color is calculated from contact's fingerprint (for key-contacts) or email address (for
1705/// address-contacts; should be lowercased to avoid allocation) and can be used for an fallback
1706/// avatar with white initials as well as for headlines in bubbles of group chats.
1707pub 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
1717// Updates the names of the chats which use the contact name.
1718//
1719// This is one of the few duplicated data, however, getting the chat list is easier this way.
1720fn 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            // Chat name updated
1763            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        // also (un)block all chats with _only_ this contact - we do not delete them to allow a
1793        // non-destructive blocking->unblocking.
1794        // (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
1795        // However, I'm not sure about this point; it may be confusing if the user wants to add other people;
1796        // this would result in recreating the same group...)
1797        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        // also unblock mailinglist
1817        // if the contact is a mailinglist address explicitly created to allow unblocking
1818        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
1847/// Set profile image for a contact.
1848///
1849/// The given profile image is expected to be already in the blob directory
1850/// as profile images can be set only by receiving messages, this should be always the case, however.
1851///
1852/// For contact SELF, the image is not saved in the contact-database but as Config::Selfavatar.
1853pub(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
1889/// Sets contact status.
1890///
1891/// For contact SELF, the status is not saved in the contact table, but as Config::Selfstatus.
1892pub(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/// Updates last seen timestamp of the contact if it is earlier than the given `timestamp`.
1914#[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
1944/// Marks contact `contact_id` as verified by `verifier_id`.
1945///
1946/// `verifier_id == None` means that the verifier is unknown.
1947pub(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                    // Avoid introducing incorrect reverse chains: if the verifier itself has an
1989                    // unknown verifier, it may be `contact_id` actually (directly or indirectly) on
1990                    // the other device (which is needed for getting "verified by unknown contact"
1991                    // in the first place).
1992                    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    /// Task running "recently seen" loop.
2032    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        // Priority contains all recently seen sorted by the timestamp
2059        // when they become not recently seen.
2060        //
2061        // Initialize with contacts which are currently seen, but will
2062        // become unseen in the future.
2063        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                    // Sleep for 24 hours.
2090                    (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                        // Timeout, notify about contact.
2103                        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                        // Maybe the sender side is closed.
2119                        // Terminate the loop to avoid looping indefinitely.
2120                        return;
2121                    }
2122                    Ok(Ok(RecentlySeenInterrupt {
2123                        contact_id,
2124                        timestamp,
2125                    })) => {
2126                        // Received an interrupt.
2127                        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                // Event is already in the past.
2140                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        // Await aborted task to ensure the `Future` is dropped
2177        // with all resources moved inside such as the `Context`
2178        // reference to `InnerContext`.
2179        self.handle.await.ok();
2180    }
2181}
2182
2183#[cfg(test)]
2184mod contact_tests;