Skip to main content

egs_api/api/
mod.rs

1use log::{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        let response = self
120            .authorized_get_client(parsed_url)
121            .send()
122            .await
123            .map_err(|e| {
124                error!("{:?}", e);
125                EpicAPIError::NetworkError(e)
126            })?;
127        if response.status() == reqwest::StatusCode::OK {
128            response.json::<T>().await.map_err(|e| {
129                error!("{:?}", e);
130                EpicAPIError::DeserializationError(format!("{}", e))
131            })
132        } else {
133            let status = response.status();
134            let body = response.text().await.unwrap_or_default();
135            warn!("{} result: {}", status, body);
136            Err(EpicAPIError::HttpError { status, body })
137        }
138    }
139
140    /// Send an authorized POST request with form data and deserialize the JSON response
141    pub(crate) async fn authorized_post_form_json<T: DeserializeOwned>(
142        &self,
143        url: &str,
144        form: &[(String, String)],
145    ) -> Result<T, EpicAPIError> {
146        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
147        let response = self
148            .authorized_post_client(parsed_url)
149            .form(form)
150            .send()
151            .await
152            .map_err(|e| {
153                error!("{:?}", e);
154                EpicAPIError::NetworkError(e)
155            })?;
156        if response.status() == reqwest::StatusCode::OK {
157            response.json::<T>().await.map_err(|e| {
158                error!("{:?}", e);
159                EpicAPIError::DeserializationError(format!("{}", e))
160            })
161        } else {
162            let status = response.status();
163            let body = response.text().await.unwrap_or_default();
164            warn!("{} result: {}", status, body);
165            Err(EpicAPIError::HttpError { status, body })
166        }
167    }
168
169    /// Send an authorized POST request with a JSON body and deserialize the JSON response
170    pub(crate) async fn authorized_post_json<T: DeserializeOwned, B: serde::Serialize>(
171        &self,
172        url: &str,
173        body: &B,
174    ) -> Result<T, EpicAPIError> {
175        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
176        let response = self
177            .authorized_post_client(parsed_url)
178            .json(body)
179            .send()
180            .await
181            .map_err(|e| {
182                error!("{:?}", e);
183                EpicAPIError::NetworkError(e)
184            })?;
185        if response.status() == reqwest::StatusCode::OK {
186            response.json::<T>().await.map_err(|e| {
187                error!("{:?}", e);
188                EpicAPIError::DeserializationError(format!("{}", e))
189            })
190        } else {
191            let status = response.status();
192            let body = response.text().await.unwrap_or_default();
193            warn!("{} result: {}", status, body);
194            Err(EpicAPIError::HttpError { status, body })
195        }
196    }
197
198    /// Send an unauthenticated GET request and return the raw bytes
199    pub(crate) async fn get_bytes(&self, url: &str) -> Result<Vec<u8>, EpicAPIError> {
200        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
201        let response = self
202            .client
203            .get(parsed_url)
204            .send()
205            .await
206            .map_err(|e| {
207                error!("{:?}", e);
208                EpicAPIError::NetworkError(e)
209            })?;
210        if response.status() == reqwest::StatusCode::OK {
211            response.bytes().await.map(|b| b.to_vec()).map_err(|e| {
212                error!("{:?}", e);
213                EpicAPIError::DeserializationError(format!("{}", e))
214            })
215        } else {
216            let status = response.status();
217            let body = response.text().await.unwrap_or_default();
218            warn!("{} result: {}", status, body);
219            Err(EpicAPIError::HttpError { status, body })
220        }
221    }
222
223    /// Send an unauthenticated GET request and deserialize the JSON response
224    pub(crate) async fn get_json<T: DeserializeOwned>(
225        &self,
226        url: &str,
227    ) -> Result<T, EpicAPIError> {
228        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
229        let response = self.client.get(parsed_url).send().await.map_err(|e| {
230            error!("{:?}", e);
231            EpicAPIError::NetworkError(e)
232        })?;
233        if response.status() == reqwest::StatusCode::OK {
234            response.json::<T>().await.map_err(|e| {
235                error!("{:?}", e);
236                EpicAPIError::DeserializationError(format!("{}", e))
237            })
238        } else {
239            let status = response.status();
240            let body = response.text().await.unwrap_or_default();
241            warn!("{} result: {}", status, body);
242            Err(EpicAPIError::HttpError { status, body })
243        }
244    }
245
246    #[allow(dead_code)]
247    /// Send an authorized DELETE request, returning Ok(()) on success
248    pub(crate) async fn authorized_delete(&self, url: &str) -> Result<(), EpicAPIError> {
249        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
250        let response = self
251            .set_authorization_header(self.client.delete(parsed_url))
252            .send()
253            .await
254            .map_err(|e| {
255                error!("{:?}", e);
256                EpicAPIError::NetworkError(e)
257            })?;
258        if response.status() == reqwest::StatusCode::OK
259            || response.status() == reqwest::StatusCode::NO_CONTENT
260        {
261            Ok(())
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}