whatsapp_rust/features/
contacts.rs1use 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
14pub 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 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}