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, info, 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, time};
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 is_changed = transaction.execute(
119                    "UPDATE contacts SET name=?1 WHERE id=?2 AND name!=?1",
120                    (name, self),
121                )? > 0;
122                if is_changed {
123                    update_chat_names(context, transaction, self)?;
124                    let (addr, fingerprint) = transaction.query_row(
125                        "SELECT addr, fingerprint FROM contacts WHERE id=?",
126                        (self,),
127                        |row| {
128                            let addr: String = row.get(0)?;
129                            let fingerprint: String = row.get(1)?;
130                            Ok((addr, fingerprint))
131                        },
132                    )?;
133                    context.emit_event(EventType::ContactsChanged(Some(self)));
134                    Ok(Some((addr, fingerprint)))
135                } else {
136                    Ok(None)
137                }
138            })
139            .await?;
140
141        if sync.into() {
142            if let Some((addr, fingerprint)) = row {
143                if fingerprint.is_empty() {
144                    chat::sync(
145                        context,
146                        chat::SyncId::ContactAddr(addr),
147                        chat::SyncAction::Rename(name.to_string()),
148                    )
149                    .await
150                    .log_err(context)
151                    .ok();
152                } else {
153                    chat::sync(
154                        context,
155                        chat::SyncId::ContactFingerprint(fingerprint),
156                        chat::SyncAction::Rename(name.to_string()),
157                    )
158                    .await
159                    .log_err(context)
160                    .ok();
161                }
162            }
163        }
164        Ok(())
165    }
166
167    /// Mark contact as bot.
168    pub(crate) async fn mark_bot(&self, context: &Context, is_bot: bool) -> Result<()> {
169        context
170            .sql
171            .execute("UPDATE contacts SET is_bot=? WHERE id=?;", (is_bot, self.0))
172            .await?;
173        Ok(())
174    }
175
176    /// Reset gossip timestamp in all chats with this contact.
177    pub(crate) async fn regossip_keys(&self, context: &Context) -> Result<()> {
178        context
179            .sql
180            .execute(
181                "UPDATE chats
182                 SET gossiped_timestamp=0
183                 WHERE EXISTS (SELECT 1 FROM chats_contacts
184                               WHERE chats_contacts.chat_id=chats.id
185                               AND chats_contacts.contact_id=?
186                               AND chats_contacts.add_timestamp >= chats_contacts.remove_timestamp)",
187                (self,),
188            )
189            .await?;
190        Ok(())
191    }
192
193    /// Updates the origin of the contacts, but only if `origin` is higher than the current one.
194    pub(crate) async fn scaleup_origin(
195        context: &Context,
196        ids: &[Self],
197        origin: Origin,
198    ) -> Result<()> {
199        context
200            .sql
201            .transaction(|transaction| {
202                let mut stmt = transaction
203                    .prepare("UPDATE contacts SET origin=?1 WHERE id = ?2 AND origin < ?1")?;
204                for id in ids {
205                    stmt.execute((origin, id))?;
206                }
207                Ok(())
208            })
209            .await?;
210        Ok(())
211    }
212
213    /// Returns contact address.
214    pub async fn addr(&self, context: &Context) -> Result<String> {
215        let addr = context
216            .sql
217            .query_row("SELECT addr FROM contacts WHERE id=?", (self,), |row| {
218                let addr: String = row.get(0)?;
219                Ok(addr)
220            })
221            .await?;
222        Ok(addr)
223    }
224}
225
226impl fmt::Display for ContactId {
227    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
228        if *self == ContactId::UNDEFINED {
229            write!(f, "Contact#Undefined")
230        } else if *self == ContactId::SELF {
231            write!(f, "Contact#Self")
232        } else if *self == ContactId::INFO {
233            write!(f, "Contact#Info")
234        } else if *self == ContactId::DEVICE {
235            write!(f, "Contact#Device")
236        } else if self.is_special() {
237            write!(f, "Contact#Special{}", self.0)
238        } else {
239            write!(f, "Contact#{}", self.0)
240        }
241    }
242}
243
244/// Allow converting [`ContactId`] to an SQLite type.
245impl rusqlite::types::ToSql for ContactId {
246    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
247        let val = rusqlite::types::Value::Integer(i64::from(self.0));
248        let out = rusqlite::types::ToSqlOutput::Owned(val);
249        Ok(out)
250    }
251}
252
253/// Allow converting an SQLite integer directly into [`ContactId`].
254impl rusqlite::types::FromSql for ContactId {
255    fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
256        i64::column_result(value).and_then(|val| {
257            val.try_into()
258                .map(ContactId::new)
259                .map_err(|_| rusqlite::types::FromSqlError::OutOfRange(val))
260        })
261    }
262}
263
264/// Returns a vCard containing contacts with the given ids.
265pub async fn make_vcard(context: &Context, contacts: &[ContactId]) -> Result<String> {
266    let now = time();
267    let mut vcard_contacts = Vec::with_capacity(contacts.len());
268    for id in contacts {
269        let c = Contact::get_by_id(context, *id).await?;
270        let key = c.public_key(context).await?.map(|k| k.to_base64());
271        let profile_image = match c.get_profile_image_ex(context, false).await? {
272            None => None,
273            Some(path) => tokio::fs::read(path)
274                .await
275                .log_err(context)
276                .ok()
277                .map(|data| base64::engine::general_purpose::STANDARD.encode(data)),
278        };
279        vcard_contacts.push(VcardContact {
280            addr: c.addr,
281            authname: c.authname,
282            key,
283            profile_image,
284            biography: Some(c.status).filter(|s| !s.is_empty()),
285            // Use the current time to not reveal our or contact's online time.
286            timestamp: Ok(now),
287        });
288    }
289
290    // XXX: newline at the end of vCard is trimmed
291    // for compatibility with core <=1.155.3
292    // Newer core should be able to deal with
293    // trailing CRLF as the fix
294    // <https://github.com/deltachat/deltachat-core-rust/pull/6522>
295    // is merged.
296    Ok(contact_tools::make_vcard(&vcard_contacts)
297        .trim_end()
298        .to_string())
299}
300
301/// Imports contacts from the given vCard.
302///
303/// Returns the ids of successfully processed contacts in the order they appear in `vcard`,
304/// regardless of whether they are just created, modified or left untouched.
305pub async fn import_vcard(context: &Context, vcard: &str) -> Result<Vec<ContactId>> {
306    let contacts = contact_tools::parse_vcard(vcard);
307    let mut contact_ids = Vec::with_capacity(contacts.len());
308    for c in &contacts {
309        let Ok(id) = import_vcard_contact(context, c)
310            .await
311            .with_context(|| format!("import_vcard_contact() failed for {}", c.addr))
312            .log_err(context)
313        else {
314            continue;
315        };
316        contact_ids.push(id);
317    }
318    Ok(contact_ids)
319}
320
321async fn import_vcard_contact(context: &Context, contact: &VcardContact) -> Result<ContactId> {
322    let addr = ContactAddress::new(&contact.addr).context("Invalid address")?;
323    // Importing a vCard is also an explicit user action like creating a chat with the contact. We
324    // mustn't use `Origin::AddressBook` here because the vCard may be created not by us, also we
325    // want `contact.authname` to be saved as the authname and not a locally given name.
326    let origin = Origin::CreateChat;
327    let key = contact.key.as_ref().and_then(|k| {
328        SignedPublicKey::from_base64(k)
329            .with_context(|| {
330                format!(
331                    "import_vcard_contact: Cannot decode key for {}",
332                    contact.addr
333                )
334            })
335            .log_err(context)
336            .ok()
337    });
338
339    let fingerprint;
340    if let Some(public_key) = key {
341        fingerprint = public_key.dc_fingerprint().hex();
342
343        context
344            .sql
345            .execute(
346                "INSERT INTO public_keys (fingerprint, public_key)
347                 VALUES (?, ?)
348                 ON CONFLICT (fingerprint)
349                 DO NOTHING",
350                (&fingerprint, public_key.to_bytes()),
351            )
352            .await?;
353    } else {
354        fingerprint = String::new();
355    }
356
357    let (id, modified) =
358        match Contact::add_or_lookup_ex(context, &contact.authname, &addr, &fingerprint, origin)
359            .await
360        {
361            Err(e) => return Err(e).context("Contact::add_or_lookup() failed"),
362            Ok((ContactId::SELF, _)) => return Ok(ContactId::SELF),
363            Ok(val) => val,
364        };
365    if modified != Modifier::None {
366        context.emit_event(EventType::ContactsChanged(Some(id)));
367    }
368    if modified != Modifier::Created {
369        return Ok(id);
370    }
371    let path = match &contact.profile_image {
372        Some(image) => match BlobObject::store_from_base64(context, image) {
373            Err(e) => {
374                warn!(
375                    context,
376                    "import_vcard_contact: Could not decode and save avatar for {}: {e:#}.",
377                    contact.addr
378                );
379                None
380            }
381            Ok(path) => Some(path),
382        },
383        None => None,
384    };
385    if let Some(path) = path {
386        // Currently this value doesn't matter as we don't import the contact of self.
387        let was_encrypted = false;
388        if let Err(e) =
389            set_profile_image(context, id, &AvatarAction::Change(path), was_encrypted).await
390        {
391            warn!(
392                context,
393                "import_vcard_contact: Could not set avatar for {}: {e:#}.", contact.addr
394            );
395        }
396    }
397    if let Some(biography) = &contact.biography {
398        if let Err(e) = set_status(context, id, biography.to_owned(), false, false).await {
399            warn!(
400                context,
401                "import_vcard_contact: Could not set biography for {}: {e:#}.", contact.addr
402            );
403        }
404    }
405    Ok(id)
406}
407
408/// An object representing a single contact in memory.
409///
410/// The contact object is not updated.
411/// If you want an update, you have to recreate the object.
412///
413/// The library makes sure
414/// only to use names _authorized_ by the contact in `To:` or `Cc:`.
415/// *Given-names* as "Daddy" or "Honey" are not used there.
416/// For this purpose, internally, two names are tracked -
417/// authorized name and given name.
418/// By default, these names are equal, but functions working with contact names
419/// only affect the given name.
420#[derive(Debug)]
421pub struct Contact {
422    /// The contact ID.
423    pub id: ContactId,
424
425    /// Contact name. It is recommended to use `Contact::get_name`,
426    /// `Contact::get_display_name` or `Contact::get_name_n_addr` to access this field.
427    /// May be empty, initially set to `authname`.
428    name: String,
429
430    /// Name authorized by the contact himself. Only this name may be spread to others,
431    /// e.g. in To:-lists. May be empty. It is recommended to use `Contact::get_authname`,
432    /// to access this field.
433    authname: String,
434
435    /// E-Mail-Address of the contact. It is recommended to use `Contact::get_addr` to access this field.
436    addr: String,
437
438    /// OpenPGP key fingerprint.
439    /// Non-empty iff the contact is a key-contact,
440    /// identified by this fingerprint.
441    fingerprint: Option<String>,
442
443    /// Blocked state. Use contact_is_blocked to access this field.
444    pub blocked: bool,
445
446    /// Time when the contact was seen last time, Unix time in seconds.
447    last_seen: i64,
448
449    /// The origin/source of the contact.
450    pub origin: Origin,
451
452    /// Parameters as Param::ProfileImage
453    pub param: Params,
454
455    /// Last seen message signature for this contact, to be displayed in the profile.
456    status: String,
457
458    /// If the contact is a bot.
459    is_bot: bool,
460}
461
462/// Possible origins of a contact.
463#[derive(
464    Debug,
465    Default,
466    Clone,
467    Copy,
468    PartialEq,
469    Eq,
470    PartialOrd,
471    Ord,
472    FromPrimitive,
473    ToPrimitive,
474    FromSql,
475    ToSql,
476)]
477#[repr(u32)]
478pub enum Origin {
479    /// Unknown origin. Can be used as a minimum origin to specify that the caller does not care
480    /// about origin of the contact.
481    #[default]
482    Unknown = 0,
483
484    /// The contact is a mailing list address, needed to unblock mailing lists
485    MailinglistAddress = 0x2,
486
487    /// Hidden on purpose, e.g. addresses with the word "noreply" in it
488    Hidden = 0x8,
489
490    /// From: of incoming messages of unknown sender
491    IncomingUnknownFrom = 0x10,
492
493    /// Cc: of incoming messages of unknown sender
494    IncomingUnknownCc = 0x20,
495
496    /// To: of incoming messages of unknown sender
497    IncomingUnknownTo = 0x40,
498
499    /// Address scanned but not verified.
500    UnhandledQrScan = 0x80,
501
502    /// Address scanned from a SecureJoin QR code, but not verified yet.
503    UnhandledSecurejoinQrScan = 0x81,
504
505    /// Reply-To: of incoming message of known sender
506    /// Contacts with at least this origin value are shown in the contact list.
507    IncomingReplyTo = 0x100,
508
509    /// Cc: of incoming message of known sender
510    IncomingCc = 0x200,
511
512    /// additional To:'s of incoming message of known sender
513    IncomingTo = 0x400,
514
515    /// a chat was manually created for this user, but no message yet sent
516    CreateChat = 0x800,
517
518    /// message sent by us
519    OutgoingBcc = 0x1000,
520
521    /// message sent by us
522    OutgoingCc = 0x2000,
523
524    /// message sent by us
525    OutgoingTo = 0x4000,
526
527    /// internal use
528    Internal = 0x40000,
529
530    /// address is in our address book
531    AddressBook = 0x80000,
532
533    /// 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() !
534    SecurejoinInvited = 0x0100_0000,
535
536    /// Set on Bob's side for contacts scanned from a QR code.
537    /// Only means the contact has been scanned from the QR code,
538    /// but does not mean that securejoin succeeded
539    /// or the key has not changed since the last scan.
540    /// Getting the current key verification status requires calling contact_is_verified() !
541    SecurejoinJoined = 0x0200_0000,
542
543    /// contact added manually by create_contact(), this should be the largest origin as otherwise the user cannot modify the names
544    ManuallyCreated = 0x0400_0000,
545}
546
547impl Origin {
548    /// Contacts that are known, i. e. they came in via accepted contacts or
549    /// themselves an accepted contact. Known contacts are shown in the
550    /// contact list when one creates a chat and wants to add members etc.
551    pub fn is_known(self) -> bool {
552        self >= Origin::IncomingReplyTo
553    }
554}
555
556#[derive(Debug, PartialEq, Eq, Clone, Copy)]
557pub(crate) enum Modifier {
558    None,
559    Modified,
560    Created,
561}
562
563impl Contact {
564    /// Loads a single contact object from the database.
565    ///
566    /// Returns an error if the contact does not exist.
567    ///
568    /// For contact ContactId::SELF (1), the function returns sth.
569    /// like "Me" in the selected language and the email address
570    /// defined by set_config().
571    ///
572    /// For contact ContactId::DEVICE, the function overrides
573    /// the contact name and status with localized address.
574    pub async fn get_by_id(context: &Context, contact_id: ContactId) -> Result<Self> {
575        let contact = Self::get_by_id_optional(context, contact_id)
576            .await?
577            .with_context(|| format!("contact {contact_id} not found"))?;
578        Ok(contact)
579    }
580
581    /// Loads a single contact object from the database.
582    ///
583    /// Similar to [`Contact::get_by_id()`] but returns `None` if the contact does not exist.
584    pub async fn get_by_id_optional(
585        context: &Context,
586        contact_id: ContactId,
587    ) -> Result<Option<Self>> {
588        if let Some(mut contact) = context
589            .sql
590            .query_row_optional(
591                "SELECT c.name, c.addr, c.origin, c.blocked, c.last_seen,
592                c.authname, c.param, c.status, c.is_bot, c.fingerprint
593               FROM contacts c
594              WHERE c.id=?;",
595                (contact_id,),
596                |row| {
597                    let name: String = row.get(0)?;
598                    let addr: String = row.get(1)?;
599                    let origin: Origin = row.get(2)?;
600                    let blocked: Option<bool> = row.get(3)?;
601                    let last_seen: i64 = row.get(4)?;
602                    let authname: String = row.get(5)?;
603                    let param: String = row.get(6)?;
604                    let status: Option<String> = row.get(7)?;
605                    let is_bot: bool = row.get(8)?;
606                    let fingerprint: Option<String> =
607                        Some(row.get(9)?).filter(|s: &String| !s.is_empty());
608                    let contact = Self {
609                        id: contact_id,
610                        name,
611                        authname,
612                        addr,
613                        fingerprint,
614                        blocked: blocked.unwrap_or_default(),
615                        last_seen,
616                        origin,
617                        param: param.parse().unwrap_or_default(),
618                        status: status.unwrap_or_default(),
619                        is_bot,
620                    };
621                    Ok(contact)
622                },
623            )
624            .await?
625        {
626            if contact_id == ContactId::SELF {
627                contact.name = stock_str::self_msg(context).await;
628                contact.authname = context
629                    .get_config(Config::Displayname)
630                    .await?
631                    .unwrap_or_default();
632                contact.addr = context
633                    .get_config(Config::ConfiguredAddr)
634                    .await?
635                    .unwrap_or_default();
636                if let Some(self_fp) = self_fingerprint_opt(context).await? {
637                    contact.fingerprint = Some(self_fp.to_string());
638                }
639                contact.status = context
640                    .get_config(Config::Selfstatus)
641                    .await?
642                    .unwrap_or_default();
643            } else if contact_id == ContactId::DEVICE {
644                contact.name = stock_str::device_messages(context).await;
645                contact.addr = ContactId::DEVICE_ADDR.to_string();
646                contact.status = stock_str::device_messages_hint(context).await;
647            }
648            Ok(Some(contact))
649        } else {
650            Ok(None)
651        }
652    }
653
654    /// Returns `true` if this contact is blocked.
655    pub fn is_blocked(&self) -> bool {
656        self.blocked
657    }
658
659    /// Returns last seen timestamp.
660    pub fn last_seen(&self) -> i64 {
661        self.last_seen
662    }
663
664    /// Returns `true` if this contact was seen recently.
665    pub fn was_seen_recently(&self) -> bool {
666        time() - self.last_seen <= SEEN_RECENTLY_SECONDS
667    }
668
669    /// Check if a contact is blocked.
670    pub async fn is_blocked_load(context: &Context, id: ContactId) -> Result<bool> {
671        let blocked = context
672            .sql
673            .query_row("SELECT blocked FROM contacts WHERE id=?", (id,), |row| {
674                let blocked: bool = row.get(0)?;
675                Ok(blocked)
676            })
677            .await?;
678        Ok(blocked)
679    }
680
681    /// Block the given contact.
682    pub async fn block(context: &Context, id: ContactId) -> Result<()> {
683        set_blocked(context, Sync, id, true).await
684    }
685
686    /// Unblock the given contact.
687    pub async fn unblock(context: &Context, id: ContactId) -> Result<()> {
688        set_blocked(context, Sync, id, false).await
689    }
690
691    /// Add a single contact as a result of an _explicit_ user action.
692    ///
693    /// We assume, the contact name, if any, is entered by the user and is used "as is" therefore,
694    /// normalize() is *not* called for the name. If the contact is blocked, it is unblocked.
695    ///
696    /// To add a number of contacts, see `add_address_book()` which is much faster for adding
697    /// a bunch of addresses.
698    ///
699    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
700    pub async fn create(context: &Context, name: &str, addr: &str) -> Result<ContactId> {
701        Self::create_ex(context, Sync, name, addr).await
702    }
703
704    pub(crate) async fn create_ex(
705        context: &Context,
706        sync: sync::Sync,
707        name: &str,
708        addr: &str,
709    ) -> Result<ContactId> {
710        let (name, addr) = sanitize_name_and_addr(name, addr);
711        let addr = ContactAddress::new(&addr)?;
712
713        let (contact_id, sth_modified) =
714            Contact::add_or_lookup(context, &name, &addr, Origin::ManuallyCreated)
715                .await
716                .context("add_or_lookup")?;
717        let blocked = Contact::is_blocked_load(context, contact_id).await?;
718        match sth_modified {
719            Modifier::None => {}
720            Modifier::Modified | Modifier::Created => {
721                context.emit_event(EventType::ContactsChanged(Some(contact_id)))
722            }
723        }
724        if blocked {
725            set_blocked(context, Nosync, contact_id, false).await?;
726        }
727
728        if sync.into() && sth_modified != Modifier::None {
729            chat::sync(
730                context,
731                chat::SyncId::ContactAddr(addr.to_string()),
732                chat::SyncAction::Rename(name.to_string()),
733            )
734            .await
735            .log_err(context)
736            .ok();
737        }
738        Ok(contact_id)
739    }
740
741    /// Mark messages from a contact as noticed.
742    pub async fn mark_noticed(context: &Context, id: ContactId) -> Result<()> {
743        context
744            .sql
745            .execute(
746                "UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
747                (MessageState::InNoticed, id, MessageState::InFresh),
748            )
749            .await?;
750        Ok(())
751    }
752
753    /// Returns whether contact is a bot.
754    pub fn is_bot(&self) -> bool {
755        self.is_bot
756    }
757
758    /// Looks up a known and unblocked contact with a given e-mail address.
759    /// To get a list of all known and unblocked contacts, use contacts_get_contacts().
760    ///
761    ///
762    /// **POTENTIAL SECURITY ISSUE**: If there are multiple contacts with this address
763    /// (e.g. an address-contact and a key-contact),
764    /// this looks up the most recently seen contact,
765    /// i.e. which contact is returned depends on which contact last sent a message.
766    /// If the user just clicked on a mailto: link, then this is the best thing you can do.
767    /// But **DO NOT** internally represent contacts by their email address
768    /// and do not use this function to look them up;
769    /// otherwise this function will sometimes look up the wrong contact.
770    /// Instead, you should internally represent contacts by their ids.
771    ///
772    /// Known and unblocked contacts will be returned by `get_contacts()`.
773    ///
774    /// To validate an e-mail address independently of the contact database
775    /// use `may_be_valid_addr()`.
776    ///
777    /// Returns the contact ID of the contact belonging to the e-mail address or 0 if there is no
778    /// contact that is or was introduced by an accepted contact.
779    pub async fn lookup_id_by_addr(
780        context: &Context,
781        addr: &str,
782        min_origin: Origin,
783    ) -> Result<Option<ContactId>> {
784        Self::lookup_id_by_addr_ex(context, addr, min_origin, Some(Blocked::Not)).await
785    }
786
787    /// The same as `lookup_id_by_addr()`, but internal function. Currently also allows looking up
788    /// not unblocked contacts.
789    pub(crate) async fn lookup_id_by_addr_ex(
790        context: &Context,
791        addr: &str,
792        min_origin: Origin,
793        blocked: Option<Blocked>,
794    ) -> Result<Option<ContactId>> {
795        if addr.is_empty() {
796            bail!("lookup_id_by_addr: empty address");
797        }
798
799        let addr_normalized = addr_normalize(addr);
800
801        if context.is_configured().await? && context.is_self_addr(addr).await? {
802            return Ok(Some(ContactId::SELF));
803        }
804
805        let id = context
806            .sql
807            .query_get_value(
808                "SELECT id FROM contacts
809                 WHERE addr=?1 COLLATE NOCASE
810                     AND id>?2 AND origin>=?3 AND (? OR blocked=?)
811                 ORDER BY
812                     (
813                         SELECT COUNT(*) FROM chats c
814                         INNER JOIN chats_contacts cc
815                         ON c.id=cc.chat_id
816                         WHERE c.type=?
817                             AND c.id>?
818                             AND c.blocked=?
819                             AND cc.contact_id=contacts.id
820                     ) DESC,
821                     last_seen DESC, fingerprint DESC
822                 LIMIT 1",
823                (
824                    &addr_normalized,
825                    ContactId::LAST_SPECIAL,
826                    min_origin as u32,
827                    blocked.is_none(),
828                    blocked.unwrap_or(Blocked::Not),
829                    Chattype::Single,
830                    constants::DC_CHAT_ID_LAST_SPECIAL,
831                    blocked.unwrap_or(Blocked::Not),
832                ),
833            )
834            .await?;
835        Ok(id)
836    }
837
838    pub(crate) async fn add_or_lookup(
839        context: &Context,
840        name: &str,
841        addr: &ContactAddress,
842        origin: Origin,
843    ) -> Result<(ContactId, Modifier)> {
844        Self::add_or_lookup_ex(context, name, addr, "", origin).await
845    }
846
847    /// Lookup a contact and create it if it does not exist yet.
848    /// If `fingerprint` is non-empty, a key-contact with this fingerprint is added / looked up.
849    /// Otherwise, an address-contact with `addr` is added / looked up.
850    /// A name and an "origin" can be given.
851    ///
852    /// The "origin" is where the address comes from -
853    /// from-header, cc-header, addressbook, qr, manual-edit etc.
854    /// In general, "better" origins overwrite the names of "worse" origins -
855    /// Eg. if we got a name in cc-header and later in from-header, the name will change -
856    /// this does not happen the other way round.
857    ///
858    /// The "best" origin are manually created contacts -
859    /// names given manually can only be overwritten by further manual edits
860    /// (until they are set empty again or reset to the name seen in the From-header).
861    ///
862    /// These manually edited names are _never_ used for sending on the wire -
863    /// this should avoid sending sth. as "Mama" or "Daddy" to some 3rd party.
864    /// Instead, for the wire, we use so called "authnames"
865    /// that can only be set and updated by a From-header.
866    ///
867    /// The different names used in the function are:
868    /// - "name": name passed as function argument, belonging to the given origin
869    /// - "row_name": current name used in the database, typically set to "name"
870    /// - "row_authname": name as authorized from a contact, set only through a From-header
871    ///   Depending on the origin, both, "row_name" and "row_authname" are updated from "name".
872    ///
873    /// Returns the contact_id and a `Modifier` value indicating if a modification occurred.
874    pub(crate) async fn add_or_lookup_ex(
875        context: &Context,
876        name: &str,
877        addr: &str,
878        fingerprint: &str,
879        mut origin: Origin,
880    ) -> Result<(ContactId, Modifier)> {
881        let mut sth_modified = Modifier::None;
882
883        ensure!(
884            !addr.is_empty() || !fingerprint.is_empty(),
885            "Can not add_or_lookup empty address"
886        );
887        ensure!(origin != Origin::Unknown, "Missing valid origin");
888
889        if context.is_configured().await? && context.is_self_addr(addr).await? {
890            return Ok((ContactId::SELF, sth_modified));
891        }
892
893        if !fingerprint.is_empty() && context.is_configured().await? {
894            let fingerprint_self = self_fingerprint(context)
895                .await
896                .context("self_fingerprint")?;
897            if fingerprint == fingerprint_self {
898                return Ok((ContactId::SELF, sth_modified));
899            }
900        }
901
902        let mut name = sanitize_name(name);
903        if origin <= Origin::OutgoingTo {
904            // The user may accidentally have written to a "noreply" address with another MUA:
905            if addr.contains("noreply")
906                || addr.contains("no-reply")
907                || addr.starts_with("notifications@")
908                // Filter out use-once addresses (like reply+AEJDGPOECLAP...@reply.github.com):
909                || (addr.len() > 50 && addr.contains('+'))
910            {
911                info!(context, "hiding contact {}", addr);
912                origin = Origin::Hidden;
913                // For these kind of email addresses, sender and address often don't belong together
914                // (like hocuri <notifications@github.com>). In this example, hocuri shouldn't
915                // be saved as the displayname for notifications@github.com.
916                name = "".to_string();
917            }
918        }
919
920        // If the origin indicates that user entered the contact manually, from the address book or
921        // from the QR-code scan (potentially from the address book of their other phone), then name
922        // should go into the "name" column and never into "authname" column, to avoid leaking it
923        // into the network.
924        let manual = matches!(
925            origin,
926            Origin::ManuallyCreated | Origin::AddressBook | Origin::UnhandledQrScan
927        );
928
929        let mut update_addr = false;
930
931        let row_id = context
932            .sql
933            .transaction(|transaction| {
934                let row = transaction
935                    .query_row(
936                        "SELECT id, name, addr, origin, authname
937                         FROM contacts
938                         WHERE fingerprint=?1 AND
939                         (?1<>'' OR addr=?2 COLLATE NOCASE)",
940                        (fingerprint, addr),
941                        |row| {
942                            let row_id: u32 = row.get(0)?;
943                            let row_name: String = row.get(1)?;
944                            let row_addr: String = row.get(2)?;
945                            let row_origin: Origin = row.get(3)?;
946                            let row_authname: String = row.get(4)?;
947
948                            Ok((row_id, row_name, row_addr, row_origin, row_authname))
949                        },
950                    )
951                    .optional()?;
952
953                let row_id;
954                if let Some((id, row_name, row_addr, row_origin, row_authname)) = row {
955                    let update_name = manual && name != row_name;
956                    let update_authname = !manual
957                        && name != row_authname
958                        && !name.is_empty()
959                        && (origin >= row_origin
960                            || origin == Origin::IncomingUnknownFrom
961                            || row_authname.is_empty());
962
963                    row_id = id;
964                    if origin >= row_origin && addr != row_addr {
965                        update_addr = true;
966                    }
967                    if update_name || update_authname || update_addr || origin > row_origin {
968                        let new_name = if update_name {
969                            name.to_string()
970                        } else {
971                            row_name
972                        };
973
974                        transaction.execute(
975                            "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
976                            (
977                                new_name,
978                                if update_addr {
979                                    addr.to_string()
980                                } else {
981                                    row_addr
982                                },
983                                if origin > row_origin {
984                                    origin
985                                } else {
986                                    row_origin
987                                },
988                                if update_authname {
989                                    name.to_string()
990                                } else {
991                                    row_authname
992                                },
993                                row_id,
994                            ),
995                        )?;
996
997                        if update_name || update_authname {
998                            let contact_id = ContactId::new(row_id);
999                            update_chat_names(context, transaction, contact_id)?;
1000                        }
1001                        sth_modified = Modifier::Modified;
1002                    }
1003                } else {
1004                    let update_name = manual;
1005                    let update_authname = !manual;
1006
1007                    transaction.execute(
1008                        "INSERT INTO contacts (name, addr, fingerprint, origin, authname)
1009                         VALUES (?, ?, ?, ?, ?);",
1010                        (
1011                            if update_name { &name } else { "" },
1012                            &addr,
1013                            fingerprint,
1014                            origin,
1015                            if update_authname { &name } else { "" },
1016                        ),
1017                    )?;
1018
1019                    sth_modified = Modifier::Created;
1020                    row_id = u32::try_from(transaction.last_insert_rowid())?;
1021                    if fingerprint.is_empty() {
1022                        info!(context, "Added contact id={row_id} addr={addr}.");
1023                    } else {
1024                        info!(
1025                            context,
1026                            "Added contact id={row_id} fpr={fingerprint} addr={addr}."
1027                        );
1028                    }
1029                }
1030                Ok(row_id)
1031            })
1032            .await?;
1033
1034        let contact_id = ContactId::new(row_id);
1035
1036        Ok((contact_id, sth_modified))
1037    }
1038
1039    /// Add a number of contacts.
1040    ///
1041    /// Typically used to add the whole address book from the OS. As names here are typically not
1042    /// well formatted, we call `normalize()` for each name given.
1043    ///
1044    /// No email-address is added twice.
1045    /// Trying to add email-addresses that are already in the contact list,
1046    /// results in updating the name unless the name was changed manually by the user.
1047    /// If any email-address or any name is really updated,
1048    /// the event `DC_EVENT_CONTACTS_CHANGED` is sent.
1049    ///
1050    /// To add a single contact entered by the user, you should prefer `Contact::create`,
1051    /// however, for adding a bunch of addresses, this function is much faster.
1052    ///
1053    /// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`.
1054    ///
1055    /// Returns the number of modified contacts.
1056    pub async fn add_address_book(context: &Context, addr_book: &str) -> Result<usize> {
1057        let mut modify_cnt = 0;
1058
1059        for (name, addr) in split_address_book(addr_book) {
1060            let (name, addr) = sanitize_name_and_addr(name, addr);
1061            match ContactAddress::new(&addr) {
1062                Ok(addr) => {
1063                    match Contact::add_or_lookup(context, &name, &addr, Origin::AddressBook).await {
1064                        Ok((_, modified)) => {
1065                            if modified != Modifier::None {
1066                                modify_cnt += 1
1067                            }
1068                        }
1069                        Err(err) => {
1070                            warn!(
1071                                context,
1072                                "Failed to add address {} from address book: {}", addr, err
1073                            );
1074                        }
1075                    }
1076                }
1077                Err(err) => {
1078                    warn!(context, "{:#}.", err);
1079                }
1080            }
1081        }
1082        if modify_cnt > 0 {
1083            context.emit_event(EventType::ContactsChanged(None));
1084        }
1085
1086        Ok(modify_cnt)
1087    }
1088
1089    /// Returns known and unblocked contacts.
1090    ///
1091    /// To get information about a single contact, see get_contact().
1092    /// By default, key-contacts are listed.
1093    ///
1094    /// * `listflags` - A combination of flags:
1095    ///   - `DC_GCL_ADD_SELF` - Add SELF unless filtered by other parameters.
1096    ///   - `DC_GCL_ADDRESS` - List address-contacts instead of key-contacts.
1097    /// * `query` - A string to filter the list.
1098    pub async fn get_all(
1099        context: &Context,
1100        listflags: u32,
1101        query: Option<&str>,
1102    ) -> Result<Vec<ContactId>> {
1103        let self_addrs = context
1104            .get_all_self_addrs()
1105            .await?
1106            .into_iter()
1107            .collect::<HashSet<_>>();
1108        let mut add_self = false;
1109        let mut ret = Vec::new();
1110        let flag_add_self = (listflags & constants::DC_GCL_ADD_SELF) != 0;
1111        let flag_address = (listflags & constants::DC_GCL_ADDRESS) != 0;
1112        let minimal_origin = if context.get_config_bool(Config::Bot).await? {
1113            Origin::Unknown
1114        } else {
1115            Origin::IncomingReplyTo
1116        };
1117        if query.is_some() {
1118            let s3str_like_cmd = format!("%{}%", query.unwrap_or(""));
1119            context
1120                .sql
1121                .query_map(
1122                    "SELECT c.id, c.addr FROM contacts c
1123                 WHERE c.id>?
1124                 AND (c.fingerprint='')=?
1125                 AND c.origin>=? \
1126                 AND c.blocked=0 \
1127                 AND (iif(c.name='',c.authname,c.name) LIKE ? OR c.addr LIKE ?) \
1128                 ORDER BY c.last_seen DESC, c.id DESC;",
1129                    (
1130                        ContactId::LAST_SPECIAL,
1131                        flag_address,
1132                        minimal_origin,
1133                        &s3str_like_cmd,
1134                        &s3str_like_cmd,
1135                    ),
1136                    |row| {
1137                        let id: ContactId = row.get(0)?;
1138                        let addr: String = row.get(1)?;
1139                        Ok((id, addr))
1140                    },
1141                    |rows| {
1142                        for row in rows {
1143                            let (id, addr) = row?;
1144                            if !self_addrs.contains(&addr) {
1145                                ret.push(id);
1146                            }
1147                        }
1148                        Ok(())
1149                    },
1150                )
1151                .await?;
1152
1153            if let Some(query) = query {
1154                let self_addr = context
1155                    .get_config(Config::ConfiguredAddr)
1156                    .await?
1157                    .unwrap_or_default();
1158                let self_name = context
1159                    .get_config(Config::Displayname)
1160                    .await?
1161                    .unwrap_or_default();
1162                let self_name2 = stock_str::self_msg(context);
1163
1164                if self_addr.contains(query)
1165                    || self_name.contains(query)
1166                    || self_name2.await.contains(query)
1167                {
1168                    add_self = true;
1169                }
1170            } else {
1171                add_self = true;
1172            }
1173        } else {
1174            add_self = true;
1175
1176            context
1177                .sql
1178                .query_map(
1179                    "SELECT id, addr FROM contacts
1180                 WHERE id>?
1181                 AND (fingerprint='')=?
1182                 AND origin>=?
1183                 AND blocked=0
1184                 ORDER BY last_seen DESC, id DESC;",
1185                    (ContactId::LAST_SPECIAL, flag_address, minimal_origin),
1186                    |row| {
1187                        let id: ContactId = row.get(0)?;
1188                        let addr: String = row.get(1)?;
1189                        Ok((id, addr))
1190                    },
1191                    |rows| {
1192                        for row in rows {
1193                            let (id, addr) = row?;
1194                            if !self_addrs.contains(&addr) {
1195                                ret.push(id);
1196                            }
1197                        }
1198                        Ok(())
1199                    },
1200                )
1201                .await?;
1202        }
1203
1204        if flag_add_self && add_self {
1205            ret.push(ContactId::SELF);
1206        }
1207
1208        Ok(ret)
1209    }
1210
1211    /// Adds blocked mailinglists and broadcast channels as pseudo-contacts
1212    /// to allow unblocking them as if they are contacts
1213    /// (this way, only one unblock-ffi is needed and only one set of ui-functions,
1214    /// from the users perspective,
1215    /// there is not much difference in an email- and a mailinglist-address)
1216    async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
1217        context
1218            .sql
1219            .transaction(move |transaction| {
1220                let mut stmt = transaction.prepare(
1221                    "SELECT name, grpid, type FROM chats WHERE (type=? OR type=?) AND blocked=?",
1222                )?;
1223                let rows = stmt.query_map(
1224                    (Chattype::Mailinglist, Chattype::InBroadcast, Blocked::Yes),
1225                    |row| {
1226                        let name: String = row.get(0)?;
1227                        let grpid: String = row.get(1)?;
1228                        let typ: Chattype = row.get(2)?;
1229                        Ok((name, grpid, typ))
1230                    },
1231                )?;
1232                let blocked_mailinglists = rows.collect::<std::result::Result<Vec<_>, _>>()?;
1233                for (name, grpid, typ) in blocked_mailinglists {
1234                    let count = transaction.query_row(
1235                        "SELECT COUNT(id) FROM contacts WHERE addr=?",
1236                        [&grpid],
1237                        |row| {
1238                            let count: isize = row.get(0)?;
1239                            Ok(count)
1240                        },
1241                    )?;
1242                    if count == 0 {
1243                        transaction.execute("INSERT INTO contacts (addr) VALUES (?)", [&grpid])?;
1244                    }
1245
1246                    let fingerprint = if typ == Chattype::InBroadcast {
1247                        // Set some fingerprint so that is_pgp_contact() returns true,
1248                        // and the contact isn't marked with a letter icon.
1249                        "Blocked_broadcast"
1250                    } else {
1251                        ""
1252                    };
1253                    // Always do an update in case the blocking is reset or name is changed.
1254                    transaction.execute(
1255                        "UPDATE contacts SET name=?, origin=?, blocked=1, fingerprint=? WHERE addr=?",
1256                        (&name, Origin::MailinglistAddress, fingerprint, &grpid),
1257                    )?;
1258                }
1259                Ok(())
1260            })
1261            .await?;
1262        Ok(())
1263    }
1264
1265    /// Returns number of blocked contacts.
1266    pub async fn get_blocked_cnt(context: &Context) -> Result<usize> {
1267        let count = context
1268            .sql
1269            .count(
1270                "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
1271                (ContactId::LAST_SPECIAL,),
1272            )
1273            .await?;
1274        Ok(count)
1275    }
1276
1277    /// Get blocked contacts.
1278    pub async fn get_all_blocked(context: &Context) -> Result<Vec<ContactId>> {
1279        Contact::update_blocked_mailinglist_contacts(context)
1280            .await
1281            .context("cannot update blocked mailinglist contacts")?;
1282
1283        let list = context
1284            .sql
1285            .query_map(
1286                "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY last_seen DESC, id DESC;",
1287                (ContactId::LAST_SPECIAL,),
1288                |row| row.get::<_, ContactId>(0),
1289                |ids| {
1290                    ids.collect::<std::result::Result<Vec<_>, _>>()
1291                        .map_err(Into::into)
1292                },
1293            )
1294            .await?;
1295        Ok(list)
1296    }
1297
1298    /// Returns a textual summary of the encryption state for the contact.
1299    ///
1300    /// This function returns a string explaining the encryption state
1301    /// of the contact and if the connection is encrypted the
1302    /// fingerprints of the keys involved.
1303    pub async fn get_encrinfo(context: &Context, contact_id: ContactId) -> Result<String> {
1304        ensure!(
1305            !contact_id.is_special(),
1306            "Can not provide encryption info for special contact"
1307        );
1308
1309        let contact = Contact::get_by_id(context, contact_id).await?;
1310        let addr = context
1311            .get_config(Config::ConfiguredAddr)
1312            .await?
1313            .unwrap_or_default();
1314
1315        let Some(fingerprint_other) = contact.fingerprint() else {
1316            return Ok(stock_str::encr_none(context).await);
1317        };
1318        let fingerprint_other = fingerprint_other.to_string();
1319
1320        let stock_message = if contact.public_key(context).await?.is_some() {
1321            stock_str::e2e_available(context).await
1322        } else {
1323            stock_str::encr_none(context).await
1324        };
1325
1326        let finger_prints = stock_str::finger_prints(context).await;
1327        let mut ret = format!("{stock_message}.\n{finger_prints}:");
1328
1329        let fingerprint_self = load_self_public_key(context)
1330            .await?
1331            .dc_fingerprint()
1332            .to_string();
1333        if addr < contact.addr {
1334            cat_fingerprint(
1335                &mut ret,
1336                &stock_str::self_msg(context).await,
1337                &addr,
1338                &fingerprint_self,
1339            );
1340            cat_fingerprint(
1341                &mut ret,
1342                contact.get_display_name(),
1343                &contact.addr,
1344                &fingerprint_other,
1345            );
1346        } else {
1347            cat_fingerprint(
1348                &mut ret,
1349                contact.get_display_name(),
1350                &contact.addr,
1351                &fingerprint_other,
1352            );
1353            cat_fingerprint(
1354                &mut ret,
1355                &stock_str::self_msg(context).await,
1356                &addr,
1357                &fingerprint_self,
1358            );
1359        }
1360
1361        Ok(ret)
1362    }
1363
1364    /// Delete a contact so that it disappears from the corresponding lists.
1365    /// Depending on whether there are ongoing chats, deletion is done by physical deletion or hiding.
1366    /// The contact is deleted from the local device.
1367    ///
1368    /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event.
1369    pub async fn delete(context: &Context, contact_id: ContactId) -> Result<()> {
1370        ensure!(!contact_id.is_special(), "Can not delete special contact");
1371
1372        context
1373            .sql
1374            .transaction(move |transaction| {
1375                // make sure, the transaction starts with a write command and becomes EXCLUSIVE by that -
1376                // upgrading later may be impossible by races.
1377                let deleted_contacts = transaction.execute(
1378                    "DELETE FROM contacts WHERE id=?
1379                     AND (SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?)=0;",
1380                    (contact_id, contact_id),
1381                )?;
1382                if deleted_contacts == 0 {
1383                    transaction.execute(
1384                        "UPDATE contacts SET origin=? WHERE id=?;",
1385                        (Origin::Hidden, contact_id),
1386                    )?;
1387                }
1388                Ok(())
1389            })
1390            .await?;
1391
1392        context.emit_event(EventType::ContactsChanged(None));
1393        Ok(())
1394    }
1395
1396    /// Updates `param` column in the database.
1397    pub async fn update_param(&self, context: &Context) -> Result<()> {
1398        context
1399            .sql
1400            .execute(
1401                "UPDATE contacts SET param=? WHERE id=?",
1402                (self.param.to_string(), self.id),
1403            )
1404            .await?;
1405        Ok(())
1406    }
1407
1408    /// Updates `status` column in the database.
1409    pub async fn update_status(&self, context: &Context) -> Result<()> {
1410        context
1411            .sql
1412            .execute(
1413                "UPDATE contacts SET status=? WHERE id=?",
1414                (&self.status, self.id),
1415            )
1416            .await?;
1417        Ok(())
1418    }
1419
1420    /// Get the ID of the contact.
1421    pub fn get_id(&self) -> ContactId {
1422        self.id
1423    }
1424
1425    /// Get email address. The email address is always set for a contact.
1426    pub fn get_addr(&self) -> &str {
1427        &self.addr
1428    }
1429
1430    /// Returns true if the contact is a key-contact.
1431    /// Otherwise it is an addresss-contact.
1432    pub fn is_key_contact(&self) -> bool {
1433        self.fingerprint.is_some()
1434    }
1435
1436    /// Returns OpenPGP fingerprint of a contact.
1437    ///
1438    /// `None` for address-contacts.
1439    pub fn fingerprint(&self) -> Option<Fingerprint> {
1440        if let Some(fingerprint) = &self.fingerprint {
1441            fingerprint.parse().ok()
1442        } else {
1443            None
1444        }
1445    }
1446
1447    /// Returns OpenPGP public key of a contact.
1448    ///
1449    /// Returns `None` if the contact is not a key-contact
1450    /// or if the key is not available.
1451    /// It is possible for a key-contact to not have a key,
1452    /// e.g. if only the fingerprint is known from a QR-code.
1453    pub async fn public_key(&self, context: &Context) -> Result<Option<SignedPublicKey>> {
1454        if self.id == ContactId::SELF {
1455            return Ok(Some(load_self_public_key(context).await?));
1456        }
1457
1458        if let Some(fingerprint) = &self.fingerprint {
1459            if let Some(public_key_bytes) = context
1460                .sql
1461                .query_row_optional(
1462                    "SELECT public_key
1463                     FROM public_keys
1464                     WHERE fingerprint=?",
1465                    (fingerprint,),
1466                    |row| {
1467                        let bytes: Vec<u8> = row.get(0)?;
1468                        Ok(bytes)
1469                    },
1470                )
1471                .await?
1472            {
1473                let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
1474                Ok(Some(public_key))
1475            } else {
1476                Ok(None)
1477            }
1478        } else {
1479            Ok(None)
1480        }
1481    }
1482
1483    /// Get name authorized by the contact.
1484    pub fn get_authname(&self) -> &str {
1485        &self.authname
1486    }
1487
1488    /// Get the contact name. This is the name as modified by the local user.
1489    /// May be an empty string.
1490    ///
1491    /// This name is typically used in a form where the user can edit the name of a contact.
1492    /// To get a fine name to display in lists etc., use `Contact::get_display_name` or `Contact::get_name_n_addr`.
1493    pub fn get_name(&self) -> &str {
1494        &self.name
1495    }
1496
1497    /// Get display name. This is the name as defined by the contact himself,
1498    /// modified by the user or, if both are unset, the email address.
1499    ///
1500    /// This name is typically used in lists.
1501    /// To get the name editable in a formular, use `Contact::get_name`.
1502    pub fn get_display_name(&self) -> &str {
1503        if !self.name.is_empty() {
1504            return &self.name;
1505        }
1506        if !self.authname.is_empty() {
1507            return &self.authname;
1508        }
1509        &self.addr
1510    }
1511
1512    /// Get authorized name or address.
1513    ///
1514    /// This string is suitable for sending over email
1515    /// as it does not leak the locally set name.
1516    pub(crate) fn get_authname_or_addr(&self) -> String {
1517        if !self.authname.is_empty() {
1518            (&self.authname).into()
1519        } else {
1520            (&self.addr).into()
1521        }
1522    }
1523
1524    /// Get a summary of name and address.
1525    ///
1526    /// The returned string is either "Name (email@domain.com)" or just
1527    /// "email@domain.com" if the name is unset.
1528    ///
1529    /// The result should only be used locally and never sent over the network
1530    /// as it leaks the local contact name.
1531    ///
1532    /// The summary is typically used when asking the user something about the contact.
1533    /// The attached email address makes the question unique, eg. "Chat with Alan Miller (am@uniquedomain.com)?"
1534    pub fn get_name_n_addr(&self) -> String {
1535        if !self.name.is_empty() {
1536            format!("{} ({})", self.name, self.addr)
1537        } else if !self.authname.is_empty() {
1538            format!("{} ({})", self.authname, self.addr)
1539        } else {
1540            (&self.addr).into()
1541        }
1542    }
1543
1544    /// Get the contact's profile image.
1545    /// This is the image set by each remote user on their own
1546    /// using set_config(context, "selfavatar", image).
1547    pub async fn get_profile_image(&self, context: &Context) -> Result<Option<PathBuf>> {
1548        self.get_profile_image_ex(context, true).await
1549    }
1550
1551    /// Get the contact's profile image.
1552    /// This is the image set by each remote user on their own
1553    /// using set_config(context, "selfavatar", image).
1554    async fn get_profile_image_ex(
1555        &self,
1556        context: &Context,
1557        show_fallback_icon: bool,
1558    ) -> Result<Option<PathBuf>> {
1559        if self.id == ContactId::SELF {
1560            if let Some(p) = context.get_config(Config::Selfavatar).await? {
1561                return Ok(Some(PathBuf::from(p))); // get_config() calls get_abs_path() internally already
1562            }
1563        } else if self.id == ContactId::DEVICE {
1564            return Ok(Some(chat::get_device_icon(context).await?));
1565        }
1566        if show_fallback_icon && !self.id.is_special() && !self.is_key_contact() {
1567            return Ok(Some(chat::get_unencrypted_icon(context).await?));
1568        }
1569        if let Some(image_rel) = self.param.get(Param::ProfileImage) {
1570            if !image_rel.is_empty() {
1571                return Ok(Some(get_abs_path(context, Path::new(image_rel))));
1572            }
1573        }
1574        Ok(None)
1575    }
1576
1577    /// Get a color for the contact.
1578    /// The color is calculated from the contact's fingerprint (for key-contacts)
1579    /// or email address (for address-contacts) and can be used
1580    /// for an fallback avatar with white initials
1581    /// as well as for headlines in bubbles of group chats.
1582    pub fn get_color(&self) -> u32 {
1583        if let Some(fingerprint) = self.fingerprint() {
1584            str_to_color(&fingerprint.hex())
1585        } else {
1586            str_to_color(&self.addr.to_lowercase())
1587        }
1588    }
1589
1590    /// Gets the contact's status.
1591    ///
1592    /// Status is the last signature received in a message from this contact.
1593    pub fn get_status(&self) -> &str {
1594        self.status.as_str()
1595    }
1596
1597    /// Returns whether end-to-end encryption to the contact is available.
1598    pub async fn e2ee_avail(&self, context: &Context) -> Result<bool> {
1599        if self.id == ContactId::SELF {
1600            // We don't need to check if we have our own key.
1601            return Ok(true);
1602        }
1603        Ok(self.public_key(context).await?.is_some())
1604    }
1605
1606    /// Returns true if the contact
1607    /// can be added to verified chats.
1608    ///
1609    /// If contact is verified
1610    /// UI should display green checkmark after the contact name
1611    /// in contact list items and
1612    /// in chat member list items.
1613    ///
1614    /// In contact profile view, use this function only if there is no chat with the contact,
1615    /// otherwise use is_chat_protected().
1616    /// Use [Self::get_verifier_id] to display the verifier contact
1617    /// in the info section of the contact profile.
1618    pub async fn is_verified(&self, context: &Context) -> Result<bool> {
1619        // We're always sort of secured-verified as we could verify the key on this device any time with the key
1620        // on this device
1621        if self.id == ContactId::SELF {
1622            return Ok(true);
1623        }
1624
1625        Ok(self.get_verifier_id(context).await?.is_some())
1626    }
1627
1628    /// Returns the `ContactId` that verified the contact.
1629    ///
1630    /// If this returns Some(_),
1631    /// display green checkmark in the profile and "Introduced by ..." line
1632    /// with the name and address of the contact
1633    /// formatted by [Self::get_name_n_addr].
1634    ///
1635    /// If this returns `Some(None)`, then the contact is verified,
1636    /// but it's unclear by whom.
1637    pub async fn get_verifier_id(&self, context: &Context) -> Result<Option<Option<ContactId>>> {
1638        let verifier_id: u32 = context
1639            .sql
1640            .query_get_value("SELECT verifier FROM contacts WHERE id=?", (self.id,))
1641            .await?
1642            .with_context(|| format!("Contact {} does not exist", self.id))?;
1643
1644        if verifier_id == 0 {
1645            Ok(None)
1646        } else if verifier_id == self.id.to_u32() {
1647            Ok(Some(None))
1648        } else {
1649            Ok(Some(Some(ContactId::new(verifier_id))))
1650        }
1651    }
1652
1653    /// Returns the number of real (i.e. non-special) contacts in the database.
1654    pub async fn get_real_cnt(context: &Context) -> Result<usize> {
1655        if !context.sql.is_open().await {
1656            return Ok(0);
1657        }
1658
1659        let count = context
1660            .sql
1661            .count(
1662                "SELECT COUNT(*) FROM contacts WHERE id>?;",
1663                (ContactId::LAST_SPECIAL,),
1664            )
1665            .await?;
1666        Ok(count)
1667    }
1668
1669    /// Returns true if a contact with this ID exists.
1670    pub async fn real_exists_by_id(context: &Context, contact_id: ContactId) -> Result<bool> {
1671        if contact_id.is_special() {
1672            return Ok(false);
1673        }
1674
1675        let exists = context
1676            .sql
1677            .exists("SELECT COUNT(*) FROM contacts WHERE id=?;", (contact_id,))
1678            .await?;
1679        Ok(exists)
1680    }
1681}
1682
1683// Updates the names of the chats which use the contact name.
1684//
1685// This is one of the few duplicated data, however, getting the chat list is easier this way.
1686fn update_chat_names(
1687    context: &Context,
1688    transaction: &rusqlite::Connection,
1689    contact_id: ContactId,
1690) -> Result<()> {
1691    let chat_id: Option<ChatId> = transaction.query_row(
1692            "SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
1693            (Chattype::Single, contact_id),
1694            |row| {
1695                let chat_id: ChatId = row.get(0)?;
1696                Ok(chat_id)
1697            }
1698        ).optional()?;
1699
1700    if let Some(chat_id) = chat_id {
1701        let (addr, name, authname) = transaction.query_row(
1702            "SELECT addr, name, authname
1703                     FROM contacts
1704                     WHERE id=?",
1705            (contact_id,),
1706            |row| {
1707                let addr: String = row.get(0)?;
1708                let name: String = row.get(1)?;
1709                let authname: String = row.get(2)?;
1710                Ok((addr, name, authname))
1711            },
1712        )?;
1713
1714        let chat_name = if !name.is_empty() {
1715            name
1716        } else if !authname.is_empty() {
1717            authname
1718        } else {
1719            addr
1720        };
1721
1722        let count = transaction.execute(
1723            "UPDATE chats SET name=?1 WHERE id=?2 AND name!=?1",
1724            (chat_name, chat_id),
1725        )?;
1726
1727        if count > 0 {
1728            // Chat name updated
1729            context.emit_event(EventType::ChatModified(chat_id));
1730            chatlist_events::emit_chatlist_items_changed_for_contact(context, contact_id);
1731        }
1732    }
1733
1734    Ok(())
1735}
1736
1737pub(crate) async fn set_blocked(
1738    context: &Context,
1739    sync: sync::Sync,
1740    contact_id: ContactId,
1741    new_blocking: bool,
1742) -> Result<()> {
1743    ensure!(
1744        !contact_id.is_special(),
1745        "Can't block special contact {}",
1746        contact_id
1747    );
1748    let contact = Contact::get_by_id(context, contact_id).await?;
1749
1750    if contact.blocked != new_blocking {
1751        context
1752            .sql
1753            .execute(
1754                "UPDATE contacts SET blocked=? WHERE id=?;",
1755                (i32::from(new_blocking), contact_id),
1756            )
1757            .await?;
1758
1759        // also (un)block all chats with _only_ this contact - we do not delete them to allow a
1760        // non-destructive blocking->unblocking.
1761        // (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
1762        // However, I'm not sure about this point; it may be confusing if the user wants to add other people;
1763        // this would result in recreating the same group...)
1764        if context
1765            .sql
1766            .execute(
1767                r#"
1768UPDATE chats
1769SET blocked=?
1770WHERE type=? AND id IN (
1771  SELECT chat_id FROM chats_contacts WHERE contact_id=?
1772);
1773"#,
1774                (new_blocking, Chattype::Single, contact_id),
1775            )
1776            .await
1777            .is_ok()
1778        {
1779            Contact::mark_noticed(context, contact_id).await?;
1780            context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1781        }
1782
1783        // also unblock mailinglist
1784        // if the contact is a mailinglist address explicitly created to allow unblocking
1785        if !new_blocking && contact.origin == Origin::MailinglistAddress {
1786            if let Some((chat_id, _, _)) =
1787                chat::get_chat_id_by_grpid(context, &contact.addr).await?
1788            {
1789                chat_id.unblock_ex(context, Nosync).await?;
1790            }
1791        }
1792
1793        if sync.into() {
1794            let action = match new_blocking {
1795                true => chat::SyncAction::Block,
1796                false => chat::SyncAction::Unblock,
1797            };
1798            let sync_id = if let Some(fingerprint) = contact.fingerprint() {
1799                chat::SyncId::ContactFingerprint(fingerprint.hex())
1800            } else {
1801                chat::SyncId::ContactAddr(contact.addr.clone())
1802            };
1803
1804            chat::sync(context, sync_id, action)
1805                .await
1806                .log_err(context)
1807                .ok();
1808        }
1809    }
1810
1811    chatlist_events::emit_chatlist_changed(context);
1812    Ok(())
1813}
1814
1815/// Set profile image for a contact.
1816///
1817/// The given profile image is expected to be already in the blob directory
1818/// as profile images can be set only by receiving messages, this should be always the case, however.
1819///
1820/// For contact SELF, the image is not saved in the contact-database but as Config::Selfavatar;
1821/// this typically happens if we see message with our own profile image.
1822pub(crate) async fn set_profile_image(
1823    context: &Context,
1824    contact_id: ContactId,
1825    profile_image: &AvatarAction,
1826    was_encrypted: bool,
1827) -> Result<()> {
1828    let mut contact = Contact::get_by_id(context, contact_id).await?;
1829    let changed = match profile_image {
1830        AvatarAction::Change(profile_image) => {
1831            if contact_id == ContactId::SELF {
1832                if was_encrypted {
1833                    context
1834                        .set_config_ex(Nosync, Config::Selfavatar, Some(profile_image))
1835                        .await?;
1836                } else {
1837                    info!(context, "Do not use unencrypted selfavatar.");
1838                }
1839            } else {
1840                contact.param.set(Param::ProfileImage, profile_image);
1841            }
1842            true
1843        }
1844        AvatarAction::Delete => {
1845            if contact_id == ContactId::SELF {
1846                if was_encrypted {
1847                    context
1848                        .set_config_ex(Nosync, Config::Selfavatar, None)
1849                        .await?;
1850                } else {
1851                    info!(context, "Do not use unencrypted selfavatar deletion.");
1852                }
1853            } else {
1854                contact.param.remove(Param::ProfileImage);
1855            }
1856            true
1857        }
1858    };
1859    if changed {
1860        contact.update_param(context).await?;
1861        context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1862        chatlist_events::emit_chatlist_item_changed_for_contact_chat(context, contact_id).await;
1863    }
1864    Ok(())
1865}
1866
1867/// Sets contact status.
1868///
1869/// For contact SELF, the status is not saved in the contact table, but as Config::Selfstatus.  This
1870/// is only done if message is sent from Delta Chat and it is encrypted, to synchronize signature
1871/// between Delta Chat devices.
1872pub(crate) async fn set_status(
1873    context: &Context,
1874    contact_id: ContactId,
1875    status: String,
1876    encrypted: bool,
1877    has_chat_version: bool,
1878) -> Result<()> {
1879    if contact_id == ContactId::SELF {
1880        if encrypted && has_chat_version {
1881            context
1882                .set_config_ex(Nosync, Config::Selfstatus, Some(&status))
1883                .await?;
1884        }
1885    } else {
1886        let mut contact = Contact::get_by_id(context, contact_id).await?;
1887
1888        if contact.status != status {
1889            contact.status = status;
1890            contact.update_status(context).await?;
1891            context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1892        }
1893    }
1894    Ok(())
1895}
1896
1897/// Updates last seen timestamp of the contact if it is earlier than the given `timestamp`.
1898pub(crate) async fn update_last_seen(
1899    context: &Context,
1900    contact_id: ContactId,
1901    timestamp: i64,
1902) -> Result<()> {
1903    ensure!(
1904        !contact_id.is_special(),
1905        "Can not update special contact last seen timestamp"
1906    );
1907
1908    if context
1909        .sql
1910        .execute(
1911            "UPDATE contacts SET last_seen = ?1 WHERE last_seen < ?1 AND id = ?2",
1912            (timestamp, contact_id),
1913        )
1914        .await?
1915        > 0
1916        && timestamp > time() - SEEN_RECENTLY_SECONDS
1917    {
1918        context.emit_event(EventType::ContactsChanged(Some(contact_id)));
1919        context
1920            .scheduler
1921            .interrupt_recently_seen(contact_id, timestamp)
1922            .await;
1923    }
1924    Ok(())
1925}
1926
1927/// Marks contact `contact_id` as verified by `verifier_id`.
1928///
1929/// `verifier_id == None` means that the verifier is unknown.
1930pub(crate) async fn mark_contact_id_as_verified(
1931    context: &Context,
1932    contact_id: ContactId,
1933    verifier_id: Option<ContactId>,
1934) -> Result<()> {
1935    ensure_and_debug_assert_ne!(contact_id, ContactId::SELF,);
1936    ensure_and_debug_assert_ne!(
1937        Some(contact_id),
1938        verifier_id,
1939        "Contact cannot be verified by self",
1940    );
1941    let by_self = verifier_id == Some(ContactId::SELF);
1942    let mut verifier_id = verifier_id.unwrap_or(contact_id);
1943    context
1944        .sql
1945        .transaction(|transaction| {
1946            let contact_fingerprint: String = transaction.query_row(
1947                "SELECT fingerprint FROM contacts WHERE id=?",
1948                (contact_id,),
1949                |row| row.get(0),
1950            )?;
1951            if contact_fingerprint.is_empty() {
1952                bail!("Non-key-contact {contact_id} cannot be verified");
1953            }
1954            if verifier_id != ContactId::SELF {
1955                let (verifier_fingerprint, verifier_verifier_id): (String, ContactId) = transaction
1956                    .query_row(
1957                        "SELECT fingerprint, verifier FROM contacts WHERE id=?",
1958                        (verifier_id,),
1959                        |row| Ok((row.get(0)?, row.get(1)?)),
1960                    )?;
1961                if verifier_fingerprint.is_empty() {
1962                    bail!(
1963                        "Contact {contact_id} cannot be verified by non-key-contact {verifier_id}"
1964                    );
1965                }
1966                ensure!(
1967                    verifier_id == contact_id || verifier_verifier_id != ContactId::UNDEFINED,
1968                    "Contact {contact_id} cannot be verified by unverified contact {verifier_id}",
1969                );
1970                if verifier_verifier_id == verifier_id {
1971                    // Avoid introducing incorrect reverse chains: if the verifier itself has an
1972                    // unknown verifier, it may be `contact_id` actually (directly or indirectly) on
1973                    // the other device (which is needed for getting "verified by unknown contact"
1974                    // in the first place).
1975                    verifier_id = contact_id;
1976                }
1977            }
1978            transaction.execute(
1979                "UPDATE contacts SET verifier=?1
1980                 WHERE id=?2 AND (verifier=0 OR verifier=id OR ?3)",
1981                (verifier_id, contact_id, by_self),
1982            )?;
1983            Ok(())
1984        })
1985        .await?;
1986    Ok(())
1987}
1988
1989fn cat_fingerprint(ret: &mut String, name: &str, addr: &str, fingerprint: &str) {
1990    *ret += &format!("\n\n{name} ({addr}):\n{fingerprint}");
1991}
1992
1993fn split_address_book(book: &str) -> Vec<(&str, &str)> {
1994    book.lines()
1995        .collect::<Vec<&str>>()
1996        .chunks(2)
1997        .filter_map(|chunk| {
1998            let name = chunk.first()?;
1999            let addr = chunk.get(1)?;
2000            Some((*name, *addr))
2001        })
2002        .collect()
2003}
2004
2005#[derive(Debug)]
2006pub(crate) struct RecentlySeenInterrupt {
2007    contact_id: ContactId,
2008    timestamp: i64,
2009}
2010
2011#[derive(Debug)]
2012pub(crate) struct RecentlySeenLoop {
2013    /// Task running "recently seen" loop.
2014    handle: task::JoinHandle<()>,
2015
2016    interrupt_send: Sender<RecentlySeenInterrupt>,
2017}
2018
2019impl RecentlySeenLoop {
2020    pub(crate) fn new(context: Context) -> Self {
2021        let (interrupt_send, interrupt_recv) = channel::bounded(1);
2022
2023        let handle = task::spawn(Self::run(context, interrupt_recv));
2024        Self {
2025            handle,
2026            interrupt_send,
2027        }
2028    }
2029
2030    async fn run(context: Context, interrupt: Receiver<RecentlySeenInterrupt>) {
2031        type MyHeapElem = (Reverse<i64>, ContactId);
2032
2033        let now = SystemTime::now();
2034        let now_ts = now
2035            .duration_since(SystemTime::UNIX_EPOCH)
2036            .unwrap_or_default()
2037            .as_secs() as i64;
2038
2039        // Priority contains all recently seen sorted by the timestamp
2040        // when they become not recently seen.
2041        //
2042        // Initialize with contacts which are currently seen, but will
2043        // become unseen in the future.
2044        let mut unseen_queue: BinaryHeap<MyHeapElem> = context
2045            .sql
2046            .query_map(
2047                "SELECT id, last_seen FROM contacts
2048                 WHERE last_seen > ?",
2049                (now_ts - SEEN_RECENTLY_SECONDS,),
2050                |row| {
2051                    let contact_id: ContactId = row.get("id")?;
2052                    let last_seen: i64 = row.get("last_seen")?;
2053                    Ok((Reverse(last_seen + SEEN_RECENTLY_SECONDS), contact_id))
2054                },
2055                |rows| {
2056                    rows.collect::<std::result::Result<BinaryHeap<MyHeapElem>, _>>()
2057                        .map_err(Into::into)
2058                },
2059            )
2060            .await
2061            .unwrap_or_default();
2062
2063        loop {
2064            let now = SystemTime::now();
2065            let (until, contact_id) =
2066                if let Some((Reverse(timestamp), contact_id)) = unseen_queue.peek() {
2067                    (
2068                        UNIX_EPOCH
2069                            + Duration::from_secs((*timestamp).try_into().unwrap_or(u64::MAX))
2070                            + Duration::from_secs(1),
2071                        Some(contact_id),
2072                    )
2073                } else {
2074                    // Sleep for 24 hours.
2075                    (now + Duration::from_secs(86400), None)
2076                };
2077
2078            if let Ok(duration) = until.duration_since(now) {
2079                info!(
2080                    context,
2081                    "Recently seen loop waiting for {} or interrupt",
2082                    duration_to_str(duration)
2083                );
2084
2085                match timeout(duration, interrupt.recv()).await {
2086                    Err(_) => {
2087                        // Timeout, notify about contact.
2088                        if let Some(contact_id) = contact_id {
2089                            context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2090                            chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2091                                &context,
2092                                *contact_id,
2093                            )
2094                            .await;
2095                            unseen_queue.pop();
2096                        }
2097                    }
2098                    Ok(Err(err)) => {
2099                        warn!(
2100                            context,
2101                            "Error receiving an interruption in recently seen loop: {}", err
2102                        );
2103                        // Maybe the sender side is closed.
2104                        // Terminate the loop to avoid looping indefinitely.
2105                        return;
2106                    }
2107                    Ok(Ok(RecentlySeenInterrupt {
2108                        contact_id,
2109                        timestamp,
2110                    })) => {
2111                        // Received an interrupt.
2112                        if contact_id != ContactId::UNDEFINED {
2113                            unseen_queue
2114                                .push((Reverse(timestamp + SEEN_RECENTLY_SECONDS), contact_id));
2115                        }
2116                    }
2117                }
2118            } else {
2119                info!(
2120                    context,
2121                    "Recently seen loop is not waiting, event is already due."
2122                );
2123
2124                // Event is already in the past.
2125                if let Some(contact_id) = contact_id {
2126                    context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
2127                    chatlist_events::emit_chatlist_item_changed_for_contact_chat(
2128                        &context,
2129                        *contact_id,
2130                    )
2131                    .await;
2132                }
2133                unseen_queue.pop();
2134            }
2135        }
2136    }
2137
2138    pub(crate) fn try_interrupt(&self, contact_id: ContactId, timestamp: i64) {
2139        self.interrupt_send
2140            .try_send(RecentlySeenInterrupt {
2141                contact_id,
2142                timestamp,
2143            })
2144            .ok();
2145    }
2146
2147    #[cfg(test)]
2148    pub(crate) async fn interrupt(&self, contact_id: ContactId, timestamp: i64) {
2149        self.interrupt_send
2150            .send(RecentlySeenInterrupt {
2151                contact_id,
2152                timestamp,
2153            })
2154            .await
2155            .unwrap();
2156    }
2157
2158    pub(crate) async fn abort(self) {
2159        self.handle.abort();
2160
2161        // Await aborted task to ensure the `Future` is dropped
2162        // with all resources moved inside such as the `Context`
2163        // reference to `InnerContext`.
2164        self.handle.await.ok();
2165    }
2166}
2167
2168#[cfg(test)]
2169mod contact_tests;