bring_rs/
lib.rs

1use anyhow::{Context, Result};
2use reqwest::{
3    header::{HeaderMap, HeaderValue},
4    Client,
5};
6use serde::Deserialize;
7use std::collections::HashMap;
8
9const API_BASE_URL: &str = "https://api.getbring.com/rest/v2/";
10const API_KEY: &str = "cof4Nc6D8saplXjE3h3HXqHH8m7VU2i1Gs0g85Sp";
11
12#[derive(Debug, Clone)]
13pub struct BringClient {
14    email: String,
15    password: String,
16    client: Client,
17    base_url: String,
18    uuid: Option<String>,
19    bearer_token: Option<String>,
20    refresh_token: Option<String>,
21    name: Option<String>,
22}
23
24#[derive(Debug, Deserialize)]
25pub struct AuthResponse {
26    pub name: String,
27    pub uuid: String,
28    pub access_token: String,
29    pub refresh_token: String,
30}
31
32#[derive(Debug, Deserialize)]
33pub struct GetItemsResponseEntry {
34    pub specification: String,
35    pub name: String,
36}
37
38#[derive(Debug, Deserialize)]
39pub struct GetItemsResponse {
40    pub uuid: String,
41    pub status: String,
42    pub purchase: Vec<GetItemsResponseEntry>,
43    pub recently: Vec<GetItemsResponseEntry>,
44}
45
46#[derive(Debug, Deserialize)]
47pub struct GetAllUsersFromListEntry {
48    #[serde(rename = "publicUuid")]
49    pub public_uuid: String,
50    pub name: String,
51    pub email: String,
52    #[serde(rename = "photoPath")]
53    pub photo_path: String,
54    #[serde(rename = "pushEnabled")]
55    pub push_enabled: bool,
56    #[serde(rename = "plusTryOut")]
57    pub plus_try_out: bool,
58    pub country: String,
59    pub language: String,
60}
61
62#[derive(Debug, Deserialize)]
63pub struct GetAllUsersFromListResponse {
64    pub users: Vec<GetAllUsersFromListEntry>,
65}
66
67#[derive(Debug, Deserialize)]
68pub struct LoadListsEntry {
69    #[serde(rename = "listUuid")]
70    pub list_uuid: String,
71    pub name: String,
72    pub theme: String,
73}
74
75#[derive(Debug, Deserialize)]
76pub struct LoadListsResponse {
77    pub lists: Vec<LoadListsEntry>,
78}
79
80#[derive(Debug, Deserialize)]
81pub struct GetItemsDetailsEntry {
82    pub uuid: String,
83    #[serde(rename = "itemId")]
84    pub item_id: String,
85    #[serde(rename = "listUuid")]
86    pub list_uuid: String,
87    #[serde(rename = "userIconItemId")]
88    pub user_icon_item_id: String,
89    #[serde(rename = "userSectionId")]
90    pub user_section_id: String,
91    #[serde(rename = "assignedTo")]
92    pub assigned_to: String,
93    #[serde(rename = "imageUrl")]
94    pub image_url: String,
95}
96
97impl BringClient {
98    pub fn new(email: String, password: String) -> Self {
99        Self {
100            email,
101            password,
102            client: Client::new(),
103            base_url: API_BASE_URL.to_string(),
104            uuid: None,
105            bearer_token: None,
106            refresh_token: None,
107            name: None,
108        }
109    }
110
111    fn get_headers(&self) -> HeaderMap {
112        let mut headers = HeaderMap::new();
113        headers.insert("X-BRING-API-KEY", HeaderValue::from_static(API_KEY));
114        headers.insert("X-BRING-CLIENT", HeaderValue::from_static("webApp"));
115        headers.insert("X-BRING-CLIENT-SOURCE", HeaderValue::from_static("webApp"));
116        headers.insert("X-BRING-COUNTRY", HeaderValue::from_static("DE"));
117
118        if let Some(uuid) = &self.uuid {
119            headers.insert("X-BRING-USER-UUID", HeaderValue::from_str(uuid).unwrap());
120        }
121
122        if let Some(token) = &self.bearer_token {
123            headers.insert(
124                "Authorization",
125                HeaderValue::from_str(&format!("Bearer {}", token)).unwrap(),
126            );
127        }
128
129        headers
130    }
131
132    pub async fn login(&mut self) -> Result<()> {
133        let params = [
134            ("email", self.email.as_str()),
135            ("password", self.password.as_str()),
136        ];
137
138        let response = self
139            .client
140            .post(format!("{}bringauth", self.base_url))
141            .form(&params)
142            .send()
143            .await
144            .context("Failed to send login request")?;
145
146        let auth_response: AuthResponse = response
147            .json()
148            .await
149            .context("Failed to parse login response")?;
150
151        self.name = Some(auth_response.name);
152        self.uuid = Some(auth_response.uuid);
153        self.bearer_token = Some(auth_response.access_token);
154        self.refresh_token = Some(auth_response.refresh_token);
155
156        Ok(())
157    }
158
159    pub async fn load_lists(&self) -> Result<LoadListsResponse> {
160        let uuid = self.uuid.as_ref().context("Not logged in")?;
161
162        let response = self
163            .client
164            .get(format!("{}bringusers/{}/lists", self.base_url, uuid))
165            .headers(self.get_headers())
166            .send()
167            .await
168            .context("Failed to load lists")?;
169
170        response
171            .json()
172            .await
173            .context("Failed to parse lists response")
174    }
175
176    pub async fn get_items(&self, list_uuid: &str) -> Result<GetItemsResponse> {
177        let response = self
178            .client
179            .get(format!("{}bringlists/{}", self.base_url, list_uuid))
180            .headers(self.get_headers())
181            .send()
182            .await
183            .context("Failed to get items")?;
184
185        response
186            .json()
187            .await
188            .context("Failed to parse items response")
189    }
190
191    pub async fn get_items_details(&self, list_uuid: &str) -> Result<Vec<GetItemsDetailsEntry>> {
192        let response = self
193            .client
194            .get(format!("{}bringlists/{}/details", self.base_url, list_uuid))
195            .headers(self.get_headers())
196            .send()
197            .await
198            .context("Failed to get item details")?;
199
200        response
201            .json()
202            .await
203            .context("Failed to parse item details response")
204    }
205
206    pub async fn save_item(
207        &self,
208        list_uuid: &str,
209        item_name: &str,
210        specification: &str,
211    ) -> Result<()> {
212        let mut headers = self.get_headers();
213        headers.insert(
214            "Content-Type",
215            HeaderValue::from_static("application/x-www-form-urlencoded; charset=UTF-8"),
216        );
217
218        let params = [
219            ("purchase", item_name),
220            ("recently", ""),
221            ("specification", specification),
222            ("remove", ""),
223            ("sender", "null"),
224        ];
225
226        self.client
227            .put(format!("{}bringlists/{}", self.base_url, list_uuid))
228            .headers(headers)
229            .form(&params)
230            .send()
231            .await
232            .context("Failed to save item")?;
233
234        Ok(())
235    }
236
237    pub async fn remove_item(&self, list_uuid: &str, item_name: &str) -> Result<()> {
238        let mut headers = self.get_headers();
239        headers.insert(
240            "Content-Type",
241            HeaderValue::from_static("application/x-www-form-urlencoded; charset=UTF-8"),
242        );
243
244        let params = [
245            ("purchase", ""),
246            ("recently", ""),
247            ("specification", ""),
248            ("remove", item_name),
249            ("sender", "null"),
250        ];
251
252        self.client
253            .put(format!("{}bringlists/{}", self.base_url, list_uuid))
254            .headers(headers)
255            .form(&params)
256            .send()
257            .await
258            .context("Failed to remove item")?;
259
260        Ok(())
261    }
262
263    pub async fn get_all_users_from_list(
264        &self,
265        list_uuid: &str,
266    ) -> Result<GetAllUsersFromListResponse> {
267        let response = self
268            .client
269            .get(format!("{}bringlists/{}/users", self.base_url, list_uuid))
270            .headers(self.get_headers())
271            .send()
272            .await
273            .context("Failed to get users from list")?;
274
275        response
276            .json()
277            .await
278            .context("Failed to parse users response")
279    }
280
281    pub async fn load_translations(&self, locale: &str) -> Result<HashMap<String, String>> {
282        let response = self
283            .client
284            .get(format!(
285                "https://web.getbring.com/locale/articles.{}.json",
286                locale
287            ))
288            .send()
289            .await
290            .context("Failed to load translations")?;
291
292        response
293            .json()
294            .await
295            .context("Failed to parse translations")
296    }
297}