Skip to main content

whatsapp_rust/features/
contacts.rs

1//! Contact information feature.
2//!
3//! Profile picture types are defined in `wacore::iq::contacts`.
4//! Usync types are defined in `wacore::iq::usync`.
5
6use crate::client::Client;
7use anyhow::Result;
8use log::debug;
9use std::collections::HashMap;
10use wacore::iq::contacts::{ProfilePictureSpec, ProfilePictureType};
11use wacore::iq::usync::{ContactInfoSpec, IsOnWhatsAppSpec, UserInfoSpec};
12use wacore_binary::jid::{Jid, JidExt};
13
14// Re-export types from wacore
15pub use wacore::iq::contacts::ProfilePicture;
16pub use wacore::iq::usync::{ContactInfo, IsOnWhatsAppResult, UserInfo};
17
18pub struct Contacts<'a> {
19    client: &'a Client,
20}
21
22impl<'a> Contacts<'a> {
23    pub(crate) fn new(client: &'a Client) -> Self {
24        Self { client }
25    }
26
27    pub async fn is_on_whatsapp(&self, phones: &[&str]) -> Result<Vec<IsOnWhatsAppResult>> {
28        if phones.is_empty() {
29            return Ok(Vec::new());
30        }
31
32        debug!("is_on_whatsapp: checking {} numbers", phones.len());
33
34        let request_id = self.client.generate_request_id();
35        let phone_strings: Vec<String> = phones.iter().map(|s| s.to_string()).collect();
36        let spec = IsOnWhatsAppSpec::new(phone_strings, request_id);
37
38        Ok(self.client.execute(spec).await?)
39    }
40
41    pub async fn get_info(&self, phones: &[&str]) -> Result<Vec<ContactInfo>> {
42        if phones.is_empty() {
43            return Ok(Vec::new());
44        }
45
46        debug!("get_info: fetching info for {} numbers", phones.len());
47
48        let request_id = self.client.generate_request_id();
49        let phone_strings: Vec<String> = phones.iter().map(|s| s.to_string()).collect();
50        let spec = ContactInfoSpec::new(phone_strings, request_id);
51
52        Ok(self.client.execute(spec).await?)
53    }
54
55    pub async fn get_profile_picture(
56        &self,
57        jid: &Jid,
58        preview: bool,
59    ) -> Result<Option<ProfilePicture>> {
60        debug!(
61            "get_profile_picture: fetching {} picture for {}",
62            if preview { "preview" } else { "full" },
63            jid
64        );
65
66        let picture_type = if preview {
67            ProfilePictureType::Preview
68        } else {
69            ProfilePictureType::Full
70        };
71        let mut spec = ProfilePictureSpec::new(jid, picture_type);
72
73        // Include tctoken for user JIDs (skip groups, newsletters)
74        if !jid.is_group()
75            && !jid.is_newsletter()
76            && let Some(token) = self.client.lookup_tc_token_for_jid(jid).await
77        {
78            spec = spec.with_tc_token(token);
79        }
80
81        Ok(self.client.execute(spec).await?)
82    }
83
84    pub async fn get_user_info(&self, jids: &[Jid]) -> Result<HashMap<Jid, UserInfo>> {
85        if jids.is_empty() {
86            return Ok(HashMap::new());
87        }
88
89        debug!("get_user_info: fetching info for {} JIDs", jids.len());
90
91        let request_id = self.client.generate_request_id();
92        let spec = UserInfoSpec::new(jids.to_vec(), request_id);
93
94        Ok(self.client.execute(spec).await?)
95    }
96}
97
98impl Client {
99    pub fn contacts(&self) -> Contacts<'_> {
100        Contacts::new(self)
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_contact_info_struct() {
110        let jid: Jid = "1234567890@s.whatsapp.net"
111            .parse()
112            .expect("test JID should be valid");
113        let lid: Jid = "12345678@lid".parse().expect("test JID should be valid");
114
115        let info = ContactInfo {
116            jid: jid.clone(),
117            lid: Some(lid.clone()),
118            is_registered: true,
119            is_business: false,
120            status: Some("Hey there!".to_string()),
121            picture_id: Some(123456789),
122        };
123
124        assert!(info.is_registered);
125        assert!(!info.is_business);
126        assert_eq!(info.status, Some("Hey there!".to_string()));
127        assert_eq!(info.picture_id, Some(123456789));
128        assert!(info.lid.is_some());
129    }
130
131    #[test]
132    fn test_profile_picture_struct() {
133        let pic = ProfilePicture {
134            id: "123456789".to_string(),
135            url: "https://example.com/pic.jpg".to_string(),
136            direct_path: Some("/v/pic.jpg".to_string()),
137        };
138
139        assert_eq!(pic.id, "123456789");
140        assert_eq!(pic.url, "https://example.com/pic.jpg");
141        assert!(pic.direct_path.is_some());
142    }
143
144    #[test]
145    fn test_is_on_whatsapp_result_struct() {
146        let jid: Jid = "1234567890@s.whatsapp.net"
147            .parse()
148            .expect("test JID should be valid");
149        let result = IsOnWhatsAppResult {
150            jid,
151            is_registered: true,
152        };
153
154        assert!(result.is_registered);
155    }
156}