Skip to main content

egs_api/api/
mod.rs

1use log::{debug, error, warn};
2use reqwest::header::HeaderMap;
3use reqwest::{Client, RequestBuilder};
4use serde::de::DeserializeOwned;
5use types::account::UserData;
6use url::Url;
7
8use crate::api::error::EpicAPIError;
9
10/// Module holding the API types
11pub mod types;
12
13/// Various API Utils
14pub mod utils;
15
16/// Binary reader/writer for manifest parsing
17#[allow(dead_code)]
18pub(crate) mod binary_rw;
19
20/// Error type
21pub mod error;
22
23/// Fab Methods
24pub mod fab;
25
26///Account methods
27pub mod account;
28
29/// EGS Methods
30pub mod egs;
31#[allow(dead_code)]
32/// Cloud Save Methods
33pub mod cloud_save;
34/// Session Handling
35pub mod login;
36
37/// Commerce Methods (pricing, purchases, billing)
38pub mod commerce;
39
40/// Service Status Methods
41pub mod status;
42
43/// Presence Methods
44pub mod presence;
45
46/// Uplay/Ubisoft Store Methods
47pub mod store;
48
49/// Cosmos session and API methods (unrealengine.com cookie-based)
50pub mod cosmos;
51
52#[derive(Debug, Clone)]
53pub(crate) struct EpicAPI {
54    client: Client,
55    pub(crate) user_data: UserData,
56}
57
58impl Default for EpicAPI {
59    fn default() -> Self {
60        Self::new()
61    }
62}
63
64impl EpicAPI {
65    pub fn new() -> Self {
66        let mut headers = HeaderMap::new();
67        headers.insert(
68            "User-Agent",
69            "UELauncher/17.0.1-37584233+++Portal+Release-Live Windows/10.0.19043.1.0.64bit"
70                .parse()
71                .unwrap(),
72        );
73        headers.insert(
74            "X-Epic-Correlation-ID",
75            "UE4-c176f7154c2cda1061cc43ab52598e2b-93AFB486488A22FDF70486BD1D883628-BFCD88F649E997BA203FF69F07CE578C".parse().unwrap()
76        );
77        let client = reqwest::Client::builder()
78            .default_headers(headers)
79            .cookie_store(true)
80            .build()
81            .unwrap();
82        EpicAPI {
83            client,
84            user_data: Default::default(),
85        }
86    }
87
88    fn authorized_get_client(&self, url: Url) -> RequestBuilder {
89        self.set_authorization_header(self.client.get(url))
90    }
91
92    fn authorized_post_client(&self, url: Url) -> RequestBuilder {
93        self.set_authorization_header(self.client.post(url))
94    }
95
96    fn set_authorization_header(&self, rb: RequestBuilder) -> RequestBuilder {
97        rb.header(
98            "Authorization",
99            format!(
100                "{} {}",
101                self.user_data
102                    .token_type
103                    .as_ref()
104                    .unwrap_or(&"bearer".to_string()),
105                self.user_data
106                    .access_token
107                    .as_ref()
108                    .unwrap_or(&"".to_string())
109            ),
110        )
111    }
112
113    /// Send an authorized GET request and deserialize the JSON response
114    pub(crate) async fn authorized_get_json<T: DeserializeOwned>(
115        &self,
116        url: &str,
117    ) -> Result<T, EpicAPIError> {
118        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
119        debug!("authorized_get_json: {}", url);
120        let response = self
121            .authorized_get_client(parsed_url)
122            .send()
123            .await
124            .map_err(|e| {
125                error!("{:?}", e);
126                EpicAPIError::NetworkError(e)
127            })?;
128        if response.status() == reqwest::StatusCode::OK {
129            let body = response.text().await.map_err(|e| {
130                error!("Failed to read response body from {}: {:?}", url, e);
131                EpicAPIError::DeserializationError(format!("{}", e))
132            })?;
133            serde_json::from_str::<T>(&body).map_err(|e| {
134                error!("Deserialization failed for {}: {:?}", url, e);
135                error!("Response body: {}", &body[..body.len().min(2048)]);
136                EpicAPIError::DeserializationError(format!("{}", e))
137            })
138        } else {
139            let status = response.status();
140            let body = response.text().await.unwrap_or_default();
141            warn!("{} result: {}", status, body);
142            Err(EpicAPIError::HttpError { status, body })
143        }
144    }
145
146    /// Send an authorized POST request with form data and deserialize the JSON response
147    pub(crate) async fn authorized_post_form_json<T: DeserializeOwned>(
148        &self,
149        url: &str,
150        form: &[(String, String)],
151    ) -> Result<T, EpicAPIError> {
152        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
153        debug!("authorized_post_form_json: {}", url);
154        let response = self
155            .authorized_post_client(parsed_url)
156            .form(form)
157            .send()
158            .await
159            .map_err(|e| {
160                error!("{:?}", e);
161                EpicAPIError::NetworkError(e)
162            })?;
163        if response.status() == reqwest::StatusCode::OK {
164            let body = response.text().await.map_err(|e| {
165                error!("Failed to read response body from {}: {:?}", url, e);
166                EpicAPIError::DeserializationError(format!("{}", e))
167            })?;
168            serde_json::from_str::<T>(&body).map_err(|e| {
169                error!("Deserialization failed for {}: {:?}", url, e);
170                error!("Response body: {}", &body[..body.len().min(2048)]);
171                EpicAPIError::DeserializationError(format!("{}", e))
172            })
173        } else {
174            let status = response.status();
175            let body = response.text().await.unwrap_or_default();
176            warn!("{} result: {}", status, body);
177            Err(EpicAPIError::HttpError { status, body })
178        }
179    }
180
181    /// Send an authorized POST request with a JSON body and deserialize the JSON response
182    pub(crate) async fn authorized_post_json<T: DeserializeOwned, B: serde::Serialize>(
183        &self,
184        url: &str,
185        body: &B,
186    ) -> Result<T, EpicAPIError> {
187        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
188        debug!("authorized_post_json: {}", url);
189        let response = self
190            .authorized_post_client(parsed_url)
191            .json(body)
192            .send()
193            .await
194            .map_err(|e| {
195                error!("{:?}", e);
196                EpicAPIError::NetworkError(e)
197            })?;
198        if response.status() == reqwest::StatusCode::OK {
199            let resp_body = response.text().await.map_err(|e| {
200                error!("Failed to read response body from {}: {:?}", url, e);
201                EpicAPIError::DeserializationError(format!("{}", e))
202            })?;
203            serde_json::from_str::<T>(&resp_body).map_err(|e| {
204                error!("Deserialization failed for {}: {:?}", url, e);
205                error!("Response body: {}", &resp_body[..resp_body.len().min(2048)]);
206                EpicAPIError::DeserializationError(format!("{}", e))
207            })
208        } else {
209            let status = response.status();
210            let body = response.text().await.unwrap_or_default();
211            warn!("{} result: {}", status, body);
212            Err(EpicAPIError::HttpError { status, body })
213        }
214    }
215
216    /// Send an unauthenticated GET request and return the raw bytes
217    pub(crate) async fn get_bytes(&self, url: &str) -> Result<Vec<u8>, EpicAPIError> {
218        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
219        let response = self
220            .client
221            .get(parsed_url)
222            .send()
223            .await
224            .map_err(|e| {
225                error!("{:?}", e);
226                EpicAPIError::NetworkError(e)
227            })?;
228        if response.status() == reqwest::StatusCode::OK {
229            response.bytes().await.map(|b| b.to_vec()).map_err(|e| {
230                error!("{:?}", e);
231                EpicAPIError::DeserializationError(format!("{}", e))
232            })
233        } else {
234            let status = response.status();
235            let body = response.text().await.unwrap_or_default();
236            warn!("{} result: {}", status, body);
237            Err(EpicAPIError::HttpError { status, body })
238        }
239    }
240
241    /// Send an unauthenticated GET request and deserialize the JSON response
242    pub(crate) async fn get_json<T: DeserializeOwned>(
243        &self,
244        url: &str,
245    ) -> Result<T, EpicAPIError> {
246        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
247        debug!("get_json: {}", url);
248        let response = self.client.get(parsed_url).send().await.map_err(|e| {
249            error!("{:?}", e);
250            EpicAPIError::NetworkError(e)
251        })?;
252        if response.status() == reqwest::StatusCode::OK {
253            let body = response.text().await.map_err(|e| {
254                error!("Failed to read response body from {}: {:?}", url, e);
255                EpicAPIError::DeserializationError(format!("{}", e))
256            })?;
257            serde_json::from_str::<T>(&body).map_err(|e| {
258                error!("Deserialization failed for {}: {:?}", url, e);
259                error!("Response body: {}", &body[..body.len().min(2048)]);
260                EpicAPIError::DeserializationError(format!("{}", e))
261            })
262        } else {
263            let status = response.status();
264            let body = response.text().await.unwrap_or_default();
265            warn!("{} result: {}", status, body);
266            Err(EpicAPIError::HttpError { status, body })
267        }
268    }
269
270    #[allow(dead_code)]
271    /// Send an authorized DELETE request, returning Ok(()) on success
272    pub(crate) async fn authorized_delete(&self, url: &str) -> Result<(), EpicAPIError> {
273        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
274        let response = self
275            .set_authorization_header(self.client.delete(parsed_url))
276            .send()
277            .await
278            .map_err(|e| {
279                error!("{:?}", e);
280                EpicAPIError::NetworkError(e)
281            })?;
282        if response.status() == reqwest::StatusCode::OK
283            || response.status() == reqwest::StatusCode::NO_CONTENT
284        {
285            Ok(())
286        } else {
287            let status = response.status();
288            let body = response.text().await.unwrap_or_default();
289            warn!("{} result: {}", status, body);
290            Err(EpicAPIError::HttpError { status, body })
291        }
292    }
293}