Skip to main content

ha_api/
lib.rs

1//use reqwest::Client;
2use futures::executor;
3use std::time;
4
5pub mod errors;
6pub mod types;
7
8const CLIENT_ID: &str = "https://halcyon.casa";
9
10#[derive(Debug)]
11pub struct HomeAssistantAPI {
12    instance_urls: Vec<String>,
13    token: Option<Token>,
14    client: reqwest::Client,
15    webhook_id: Option<String>,
16    cloudhook_url: Option<String>,
17    remote_ui_url: Option<String>,
18}
19
20#[derive(Debug, Clone)]
21pub enum Token {
22    Oauth(OAuthToken),
23    LongLived(LongLivedToken),
24}
25
26#[derive(Debug, Clone)]
27pub struct OAuthToken {
28    token: String,
29    token_expiration: std::time::SystemTime,
30    refresh_token: String,
31}
32
33#[derive(Debug, Clone)]
34pub struct LongLivedToken {
35    token: String,
36}
37
38impl HomeAssistantAPI {
39    pub fn new(instance_urls: Vec<String>) -> Self {
40        return Self {
41            instance_urls: instance_urls,
42            client: reqwest::Client::new(),
43            token: None,
44            webhook_id: None,
45            cloudhook_url: None,
46            remote_ui_url: None,
47        }
48    }
49
50    pub fn auth_token(
51        &mut self,
52        oauth_token: String,
53        refresh_token: String,
54        token_expiration: u64,
55    ) -> Result<(), errors::Error> {
56        let oauth = OAuthToken {
57            token: oauth_token,
58            refresh_token: refresh_token,
59            token_expiration: (time::UNIX_EPOCH + time::Duration::from_secs(token_expiration)),
60        };
61        self.token = Some(Token::Oauth(oauth));
62        return Ok(());
63    }
64
65    pub fn auth_authorization_code(
66        &mut self,
67        authorization_code: String,
68    ) -> Result<(), errors::Error> {
69        let client = reqwest::Client::new();
70        let response = client
71            .post(self.instance_urls[0].as_str())
72            .query(&[
73                ("grant_type", "authorization_code"),
74                ("client_id", CLIENT_ID),
75                ("code", authorization_code.as_str()),
76            ])
77            .send();
78        match executor::block_on(response) {
79            Ok(response) => {
80                match response.error_for_status() {
81                    Ok(response) => {
82                        match executor::block_on(response.json::<types::AuthorizationCode>()) {
83                            Ok(response_data) => {
84                                let token_time = (time::SystemTime::now()
85                                    + time::Duration::from_secs(1800))
86                                .duration_since(time::SystemTime::UNIX_EPOCH)
87                                .unwrap()
88                                .as_secs();
89
90                                return self.auth_token(
91                                    response_data.access_token,
92                                    response_data.refresh_token,
93                                    token_time,
94                                );
95                            }
96                            Err(err) => {
97                                return Err(errors::Error::from(err));
98                            }
99                        };
100                    }
101                    Err(err) => {
102                        return Err(errors::Error::from(err));
103                    }
104                };
105            }
106            Err(err) => {
107                return Err(errors::Error::from(err));
108            }
109        };
110    }
111
112    pub fn auth_long_lived_token(
113        &mut self,
114        long_lived_token: String,
115    ) -> Result<(), errors::Error> {
116        let token = LongLivedToken {
117            token: long_lived_token,
118        };
119        self.token = Some(Token::LongLived(token));
120        return Ok(());
121    }
122
123    fn get_token(&self) -> Result<String, errors::Error> {
124        let token = self.token.clone();
125        match token {
126            Some(token) => {
127                return match token {
128                    Token::Oauth(oauth) => Ok(oauth.refresh_token),
129                    Token::LongLived(long_lived) => Ok(long_lived.token)
130                }
131            },
132            None => return Err(errors::Error::NoAuth()) 
133        }
134        
135    }
136
137    pub fn need_refresh(&self) -> bool {
138        match &self.token {
139            Some(token) => {
140                match token {
141                    Token::Oauth(oauth) => {
142                        match time::SystemTime::now().duration_since(oauth.token_expiration) {
143                            Ok(sec_left) => {
144                                if sec_left <= time::Duration::from_secs(10) {
145                                    return false;
146                                } else {
147                                    return true;
148                                }
149                            }
150                            Err(_) => return false,
151                        };
152                    },
153                    Token::LongLived(_) => {
154                        return false;
155                    }
156                }
157            },
158            None => {
159                return false;
160            }            
161        }
162    }
163
164    pub fn refresh_token(&mut self) -> Result<(), errors::Error> {
165        let token = self.token.clone(); // This is dump but I have to do it apparently
166        let refresh_token: String;
167        match token {
168            Some(token) => {
169                refresh_token = match token {
170                    Token::Oauth(oauth) => oauth.refresh_token,
171                    Token::LongLived(_) => {
172                        return Err(errors::Error::Refresh());
173                    }
174                };
175            },
176            None => return Err(errors::Error::NoAuth())
177        }
178        
179
180        let response = self
181            .client
182            .post(&self.instance_urls[0])
183            .query(&[
184                ("grant_type", "refresh_token"),
185                ("client_id", CLIENT_ID),
186                ("refresh_token", refresh_token.as_str()),
187            ])
188            .send();
189        match executor::block_on(response) {
190            Ok(response) => {
191                match response.error_for_status() {
192                    Ok(response) => {
193                        match executor::block_on(response.json::<types::RefreshToken>()) {
194                            Ok(response_data) => {
195                                let oauth = OAuthToken {
196                                    token: response_data.access_token,
197                                    token_expiration: time::SystemTime::now()
198                                        + time::Duration::from_secs(response_data.expires_in),
199                                    refresh_token: refresh_token.to_string(),
200                                };
201                                self.token = Some(Token::Oauth(oauth));
202                                return Ok(());
203                            }
204                            Err(err) => {
205                                return Err(errors::Error::from(err));
206                            }
207                        };
208                    }
209                    Err(err) => {
210                        return Err(errors::Error::from(err));
211                    }
212                };
213            }
214            Err(err) => {
215                return Err(errors::Error::from(err));
216            }
217        };
218    }
219
220    pub fn register_device(
221        &mut self,
222        device_data: types::DeviceRegistrationRequest,
223    ) -> Result<(), errors::Error> {
224        if self.need_refresh() {
225            self.refresh_token().unwrap()
226        }
227        let url = format!(
228            "{}/api/mobile_app/registrations",
229            self.instance_urls[0].as_str()
230        );
231
232        println!("{:?}", self.get_token());
233
234        let token = match self.get_token() {
235            Ok(token) => {
236                token
237            },
238            Err(err) => return Err(err) 
239        };
240
241        let response = self
242            .client
243            .post(&url)
244            .bearer_auth(token)
245            .json(&device_data);
246
247        match executor::block_on(response.send()) {
248            Ok(response) => {
249                match response.error_for_status() {
250                    Ok(response) => {
251                        match executor::block_on(
252                            response.json::<types::DeviceRegistrationResponse>(),
253                        ) {
254                            Ok(response_data) => {
255                                self.webhook_id = Some(response_data.webhook_id);
256                                self.cloudhook_url = response_data.cloudhook_url;
257                                self.remote_ui_url = response_data.remote_ui_url;
258                                return Ok(());
259                            }
260                            Err(err) => {
261                                return Err(errors::Error::from(err));
262                            }
263                        };
264                    }
265                    Err(err) => {
266                        return Err(errors::Error::from(err));
267                    }
268                };
269            }
270            Err(err) => {
271                return Err(errors::Error::from(err));
272            }
273        }
274    }
275
276    pub fn register_sensor(
277        &mut self,
278        sensor_data: types::SensorRegistrationData,
279    ) -> Result<(), errors::Error> {
280        if self.need_refresh() {
281            self.refresh_token().unwrap()
282        }
283        let register_sensor = types::SensorRegistrationRequest {
284            data: sensor_data,
285            r#type: String::from("register_sensor"),
286        };
287        match &self.webhook_id {
288            Some(webhook_id) => {
289                let url = format!(
290                    "{}/api/webhook/{}",
291                    self.instance_urls[0].as_str(),
292                    webhook_id
293                );
294
295                let token = match self.get_token() {
296                    Ok(token) => {
297                        token
298                    },
299                    Err(err) => return Err(err) 
300                };
301
302                let response = self
303                    .client
304                    .post(&url)
305                    .bearer_auth(token)
306                    .json(&register_sensor)
307                    .send();
308                match executor::block_on(response) {
309                    Ok(response) => {
310                        match response.error_for_status() {
311                            Ok(_) => {
312                                return Ok(());
313                            }
314                            Err(err) => {
315                                return Err(errors::Error::from(err));
316                            }
317                        };
318                    }
319                    Err(err) => {
320                        return Err(errors::Error::from(err));
321                    }
322                };
323            }
324            None => {
325                return Err(errors::Error::Config(String::from("Missing Webhook ID")));
326            }
327        }
328    }
329
330    pub fn update_sensor(
331        &mut self,
332        sensor_data: types::SensorUpdateData,
333    ) -> Result<(), errors::Error> {
334        if self.need_refresh() {
335            self.refresh_token().unwrap()
336        }
337        let register_sensor = types::SensorUpdateRequest {
338            data: sensor_data,
339            r#type: String::from("update_sensor_states"),
340        };
341        match &self.webhook_id {
342            Some(webhook_id) => {
343                let url = format!(
344                    "{}/api/webhook/{}",
345                    self.instance_urls[0].as_str(),
346                    webhook_id
347                );
348
349                let token = match self.get_token() {
350                    Ok(token) => {
351                        token
352                    },
353                    Err(err) => return Err(err) 
354                };
355
356                let response = self
357                    .client
358                    .post(&url)
359                    .bearer_auth(token)
360                    .json(&register_sensor)
361                    .send();
362
363                match executor::block_on(response) {
364                    Ok(response) => {
365                        match response.error_for_status() {
366                            Ok(response) => {
367                                match executor::block_on(response.json::<types::RefreshToken>()) {
368                                    Ok(_response_data) => {
369                                        return Ok(());
370                                    }
371                                    Err(err) => {
372                                        return Err(errors::Error::from(err));
373                                    }
374                                };
375                            }
376                            Err(err) => {
377                                return Err(errors::Error::from(err));
378                            }
379                        };
380                    }
381                    Err(err) => {
382                        return Err(errors::Error::from(err));
383                    }
384                };
385            }
386            None => {
387                return Err(errors::Error::Config(String::from("Missing Webhook ID")));
388            }
389        }
390    }
391}