diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 218daa929..205b9f7c2 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -2137,6 +2137,7 @@ uint32_t dc_create_contact (dc_context_t* context, const char* #define DC_GCL_DEPRECATED_VERIFIED_ONLY 0x01 #define DC_GCL_ADD_SELF 0x02 +#define DC_GCL_ADDRESS 0x04 /** @@ -2192,11 +2193,13 @@ dc_array_t* dc_import_vcard (dc_context_t* context, const char* * Returns known and unblocked contacts. * * To get information about a single contact, see dc_get_contact(). + * By default, key-contacts are listed. * * @memberof dc_context_t * @param context The context object. * @param flags A combination of flags: - * - if the flag DC_GCL_ADD_SELF is set, SELF is added to the list unless filtered by other parameters + * - DC_GCL_ADD_SELF: SELF is added to the list unless filtered by other parameters + * - DC_GCL_ADDRESS: List address-contacts instead of key-contacts. * @param query A string to filter the list. Typically used to implement an * incremental search. NULL for no filtering. * @return An array containing all contact IDs. Must be dc_array_unref()'d diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index 76dceb0a9..a7f21f9bd 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -1505,6 +1505,14 @@ impl CommandApi { Ok(contacts) } + /// Returns ids of known and unblocked contacts. + /// + /// By default, key-contacts are listed. + /// + /// * `list_flags` - A combination of flags: + /// - `DC_GCL_ADD_SELF` - Add SELF unless filtered by other parameters. + /// - `DC_GCL_ADDRESS` - List address-contacts instead of key-contacts. + /// * `query` - A string to filter the list. async fn get_contact_ids( &self, account_id: u32, @@ -1516,8 +1524,10 @@ impl CommandApi { Ok(contacts.into_iter().map(|c| c.to_u32()).collect()) } - /// Get a list of contacts. - /// (formerly called getContacts2 in desktop) + /// Returns known and unblocked contacts. + /// + /// Formerly called `getContacts2` in Desktop. + /// See [`Self::get_contact_ids`] for parameters and more info. async fn get_contacts( &self, account_id: u32, diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/const.py b/deltachat-rpc-client/src/deltachat_rpc_client/const.py index 95d1b90a8..6e62142e9 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/const.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/const.py @@ -9,6 +9,7 @@ class ContactFlag(IntEnum): """Bit flags for get_contacts() method.""" ADD_SELF = 0x02 + ADDRESS = 0x04 class ChatlistFlag(IntEnum): diff --git a/src/constants.rs b/src/constants.rs index ae5ef022b..ae7cb7fa7 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -89,6 +89,7 @@ pub const DC_GCL_ADD_ALLDONE_HINT: usize = 0x04; pub const DC_GCL_FOR_FORWARDING: usize = 0x08; pub const DC_GCL_ADD_SELF: u32 = 0x02; +pub const DC_GCL_ADDRESS: u32 = 0x04; // unchanged user avatars are resent to the recipients every some days pub(crate) const DC_RESEND_USER_AVATAR_DAYS: i64 = 14; diff --git a/src/contact.rs b/src/contact.rs index fe5618d25..c4bf4462e 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -24,7 +24,7 @@ use crate::blob::BlobObject; use crate::chat::{ChatId, ChatIdBlocked, ProtectionStatus}; use crate::color::str_to_color; use crate::config::Config; -use crate::constants::{Blocked, Chattype, DC_GCL_ADD_SELF}; +use crate::constants::{self, Blocked, Chattype}; use crate::context::Context; use crate::events::EventType; use crate::key::{ @@ -1063,11 +1063,12 @@ impl Contact { /// Returns known and unblocked contacts. /// /// To get information about a single contact, see get_contact(). + /// By default, key-contacts are listed. /// - /// `listflags` is a combination of flags: - /// - if the flag DC_GCL_ADD_SELF is set, SELF is added to the list unless filtered by other parameters - /// - /// `query` is a string to filter the list. + /// * `listflags` - A combination of flags: + /// - `DC_GCL_ADD_SELF` - Add SELF unless filtered by other parameters. + /// - `DC_GCL_ADDRESS` - List address-contacts instead of key-contacts. + /// * `query` - A string to filter the list. pub async fn get_all( context: &Context, listflags: u32, @@ -1080,7 +1081,8 @@ impl Contact { .collect::>(); let mut add_self = false; let mut ret = Vec::new(); - let flag_add_self = (listflags & DC_GCL_ADD_SELF) != 0; + let flag_add_self = (listflags & constants::DC_GCL_ADD_SELF) != 0; + let flag_address = (listflags & constants::DC_GCL_ADDRESS) != 0; let minimal_origin = if context.get_config_bool(Config::Bot).await? { Origin::Unknown } else { @@ -1093,13 +1095,14 @@ impl Contact { .query_map( "SELECT c.id, c.addr FROM contacts c WHERE c.id>? - AND c.fingerprint!='' \ + AND (c.fingerprint='')=? AND c.origin>=? \ AND c.blocked=0 \ AND (iif(c.name='',c.authname,c.name) LIKE ? OR c.addr LIKE ?) \ ORDER BY c.last_seen DESC, c.id DESC;", ( ContactId::LAST_SPECIAL, + flag_address, minimal_origin, &s3str_like_cmd, &s3str_like_cmd, @@ -1149,11 +1152,11 @@ impl Contact { .query_map( "SELECT id, addr FROM contacts WHERE id>? - AND fingerprint!='' + AND (fingerprint='')=? AND origin>=? AND blocked=0 ORDER BY last_seen DESC, id DESC;", - (ContactId::LAST_SPECIAL, minimal_origin), + (ContactId::LAST_SPECIAL, flag_address, minimal_origin), |row| { let id: ContactId = row.get(0)?; let addr: String = row.get(1)?; diff --git a/src/contact/contact_tests.rs b/src/contact/contact_tests.rs index cbc8e6032..4f5fb4c41 100644 --- a/src/contact/contact_tests.rs +++ b/src/contact/contact_tests.rs @@ -69,6 +69,9 @@ async fn test_get_contacts() -> Result<()> { let contacts = Contact::get_all(&context.ctx, 0, Some("MyName")).await?; assert_eq!(contacts.len(), 0); + let claire_id = Contact::create(&context, "someone", "claire@example.org").await?; + let dave_id = Contact::create(&context, "", "dave@example.org").await?; + let id = context.add_or_lookup_contact_id(&alice).await; assert_ne!(id, ContactId::UNDEFINED); @@ -101,10 +104,35 @@ async fn test_get_contacts() -> Result<()> { let contacts = Contact::get_all(&context, 0, Some("MyName")).await?; assert_eq!(contacts.len(), 0); - // Search by display name (same as manually set name). - let contacts = Contact::get_all(&context.ctx, 0, Some("someone")).await?; - assert_eq!(contacts.len(), 1); - assert_eq!(contacts.first(), Some(&id)); + for add_self in [0, constants::DC_GCL_ADD_SELF] { + info!(&context, "add_self={add_self}"); + + // Search key-contacts by display name (same as manually set name). + let contacts = Contact::get_all(&context.ctx, add_self, Some("someone")).await?; + assert_eq!(contacts, vec![id]); + + // Get all key-contacts. + let contacts = Contact::get_all(&context, add_self, None).await?; + match add_self { + 0 => assert_eq!(contacts, vec![id]), + _ => assert_eq!(contacts, vec![id, ContactId::SELF]), + } + } + + // Search address-contacts by display name. + let contacts = Contact::get_all(&context, constants::DC_GCL_ADDRESS, Some("someone")).await?; + assert_eq!(contacts, vec![claire_id]); + + // Get all address-contacts. Newer contacts go first. + let contacts = Contact::get_all(&context, constants::DC_GCL_ADDRESS, None).await?; + assert_eq!(contacts, vec![dave_id, claire_id]); + let contacts = Contact::get_all( + &context, + constants::DC_GCL_ADDRESS | constants::DC_GCL_ADD_SELF, + None, + ) + .await?; + assert_eq!(contacts, vec![dave_id, claire_id, ContactId::SELF]); Ok(()) }