Skip to main content

whatsapp_cloud_api/
whatsapp_client.rs

1use crate::{
2    models::{
3        CodeMethod, CodeRequestParams, CodeVerifyParams, MediaResponse, Message, MessageResponse,
4        MessageStatus, MessageStatusResponse, PhoneNumberResponse,
5    },
6    WhatsappError,
7};
8
9const FACEBOOK_GRAPH_API_BASE_URL: &str = "https://graph.facebook.com";
10
11pub struct WhatsappClient {
12    version: String,
13    access_token: String,
14    phone_number_id: String,
15}
16
17impl WhatsappClient {
18    pub fn new(access_token: &str, phone_number_id: &str) -> Self {
19        Self {
20            version: "v20.0".into(),
21            access_token: access_token.into(),
22            phone_number_id: phone_number_id.into(),
23        }
24    }
25
26    pub fn version(&mut self) -> &str {
27        &self.version
28    }
29
30    pub fn set_version(&mut self, version: &str) {
31        self.version = version.into();
32    }
33
34    pub fn set_access_token(&mut self, access_token: &str) {
35        self.access_token = access_token.into();
36    }
37
38    pub fn set_phone_number_id(&mut self, phone_number_id: &str) {
39        self.access_token = phone_number_id.into();
40    }
41
42    pub async fn send_message(&self, message: &Message) -> Result<MessageResponse, WhatsappError> {
43        http_client::post(&self.messages_api_url(), &self.access_token, message).await
44    }
45
46    pub async fn request_code(
47        &self,
48        code_method: CodeMethod,
49        language: &str,
50    ) -> Result<PhoneNumberResponse, WhatsappError> {
51        let params = CodeRequestParams {
52            code_method,
53            language: language.into(),
54        };
55        http_client::post(&self.request_code_api_url(), &self.access_token, &params).await
56    }
57
58    pub async fn verify_code(&self, code: &str) -> Result<PhoneNumberResponse, WhatsappError> {
59        let params = CodeVerifyParams { code: code.into() };
60        http_client::post(&self.verify_code_api_url(), &self.access_token, &params).await
61    }
62
63    pub async fn mark_message_as_read(
64        &self,
65        message_id: &str,
66    ) -> Result<MessageStatusResponse, WhatsappError> {
67        let message_status = MessageStatus::for_read(message_id);
68        http_client::post(
69            &self.messages_api_url(),
70            &self.access_token,
71            &message_status,
72        )
73        .await
74    }
75
76    pub async fn get_media(&self, media_id: &str) -> Result<MediaResponse, WhatsappError> {
77        http_client::get(&self.media_api_url(media_id), &self.access_token).await
78    }
79
80    fn facebook_api_version_url(&self) -> String {
81        format!("{FACEBOOK_GRAPH_API_BASE_URL}/{}", self.version)
82    }
83
84    fn messages_api_url(&self) -> String {
85        format!(
86            "{}/{}/messages",
87            self.facebook_api_version_url(),
88            self.phone_number_id
89        )
90    }
91
92    fn media_api_url(&self, media_id: &str) -> String {
93        format!("{}/{media_id}", self.facebook_api_version_url())
94    }
95
96    fn request_code_api_url(&self) -> String {
97        format!(
98            "{}/{}/request_code",
99            self.facebook_api_version_url(),
100            self.phone_number_id
101        )
102    }
103
104    fn verify_code_api_url(&self) -> String {
105        format!(
106            "{}/{}/verify_code",
107            self.facebook_api_version_url(),
108            self.phone_number_id
109        )
110    }
111}
112
113mod http_client {
114    use reqwest::StatusCode;
115    use serde::{de::DeserializeOwned, Serialize};
116
117    use crate::WhatsappError;
118
119    pub async fn get<U>(url: &str, bearer_token: &str) -> Result<U, WhatsappError>
120    where
121        U: DeserializeOwned,
122    {
123        let client = reqwest::Client::new();
124        let resp = client.get(url).bearer_auth(bearer_token).send().await?;
125
126        match resp.status() {
127            StatusCode::OK => {
128                let json = resp.json::<U>().await?;
129                Ok(json)
130            }
131            _ => {
132                log::warn!("{:?}", &resp);
133                let error_text = &resp.text().await?;
134                log::warn!("{:?}", &error_text);
135                Err(WhatsappError::UnexpectedError(error_text.to_string()))
136            }
137        }
138    }
139
140    pub async fn post<T, U>(url: &str, bearer_token: &str, data: &T) -> Result<U, WhatsappError>
141    where
142        T: Serialize + ?Sized,
143        U: DeserializeOwned,
144    {
145        let client = reqwest::Client::new();
146        let resp = client
147            .post(url)
148            .bearer_auth(bearer_token)
149            .json(&data)
150            .send()
151            .await?;
152
153        match resp.status() {
154            StatusCode::OK | StatusCode::CREATED => {
155                let json = resp.json::<U>().await?;
156                Ok(json)
157            }
158            _ => {
159                log::warn!("{:?}", &resp);
160                let error_text = &resp.text().await?;
161                log::warn!("{:?}", &error_text);
162                Err(WhatsappError::UnexpectedError(error_text.to_string()))
163            }
164        }
165    }
166}