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#[derive(Debug, Clone)]
50pub(crate) struct EpicAPI {
51    client: Client,
52    pub(crate) user_data: UserData,
53}
54
55impl Default for EpicAPI {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61impl EpicAPI {
62    pub fn new() -> Self {
63        let mut headers = HeaderMap::new();
64        headers.insert(
65            "User-Agent",
66            "UELauncher/17.0.1-37584233+++Portal+Release-Live Windows/10.0.19043.1.0.64bit"
67                .parse()
68                .unwrap(),
69        );
70        headers.insert(
71            "X-Epic-Correlation-ID",
72            "UE4-c176f7154c2cda1061cc43ab52598e2b-93AFB486488A22FDF70486BD1D883628-BFCD88F649E997BA203FF69F07CE578C".parse().unwrap()
73        );
74        let client = reqwest::Client::builder()
75            .default_headers(headers)
76            .cookie_store(true)
77            .build()
78            .unwrap();
79        EpicAPI {
80            client,
81            user_data: Default::default(),
82        }
83    }
84
85    fn authorized_get_client(&self, url: Url) -> RequestBuilder {
86        self.set_authorization_header(self.client.get(url))
87    }
88
89    fn authorized_post_client(&self, url: Url) -> RequestBuilder {
90        self.set_authorization_header(self.client.post(url))
91    }
92
93    fn set_authorization_header(&self, rb: RequestBuilder) -> RequestBuilder {
94        rb.header(
95            "Authorization",
96            format!(
97                "{} {}",
98                self.user_data
99                    .token_type
100                    .as_ref()
101                    .unwrap_or(&"bearer".to_string()),
102                self.user_data
103                    .access_token
104                    .as_ref()
105                    .unwrap_or(&"".to_string())
106            ),
107        )
108    }
109
110    /// Send an authorized GET request and deserialize the JSON response
111    pub(crate) async fn authorized_get_json<T: DeserializeOwned>(
112        &self,
113        url: &str,
114    ) -> Result<T, EpicAPIError> {
115        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
116        let response = self
117            .authorized_get_client(parsed_url)
118            .send()
119            .await
120            .map_err(|e| {
121                error!("{:?}", e);
122                EpicAPIError::NetworkError(e)
123            })?;
124        if response.status() == reqwest::StatusCode::OK {
125            response.json::<T>().await.map_err(|e| {
126                error!("{:?}", e);
127                EpicAPIError::DeserializationError(format!("{}", e))
128            })
129        } else {
130            let status = response.status();
131            let body = response.text().await.unwrap_or_default();
132            warn!("{} result: {}", status, body);
133            Err(EpicAPIError::HttpError { status, body })
134        }
135    }
136
137    /// Send an authorized POST request with form data and deserialize the JSON response
138    pub(crate) async fn authorized_post_form_json<T: DeserializeOwned>(
139        &self,
140        url: &str,
141        form: &[(String, String)],
142    ) -> Result<T, EpicAPIError> {
143        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
144        let response = self
145            .authorized_post_client(parsed_url)
146            .form(form)
147            .send()
148            .await
149            .map_err(|e| {
150                error!("{:?}", e);
151                EpicAPIError::NetworkError(e)
152            })?;
153        if response.status() == reqwest::StatusCode::OK {
154            response.json::<T>().await.map_err(|e| {
155                error!("{:?}", e);
156                EpicAPIError::DeserializationError(format!("{}", e))
157            })
158        } else {
159            let status = response.status();
160            let body = response.text().await.unwrap_or_default();
161            warn!("{} result: {}", status, body);
162            Err(EpicAPIError::HttpError { status, body })
163        }
164    }
165
166    /// Send an authorized POST request with a JSON body and deserialize the JSON response
167    pub(crate) async fn authorized_post_json<T: DeserializeOwned, B: serde::Serialize>(
168        &self,
169        url: &str,
170        body: &B,
171    ) -> Result<T, EpicAPIError> {
172        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
173        let response = self
174            .authorized_post_client(parsed_url)
175            .json(body)
176            .send()
177            .await
178            .map_err(|e| {
179                error!("{:?}", e);
180                EpicAPIError::NetworkError(e)
181            })?;
182        if response.status() == reqwest::StatusCode::OK {
183            response.json::<T>().await.map_err(|e| {
184                error!("{:?}", e);
185                EpicAPIError::DeserializationError(format!("{}", e))
186            })
187        } else {
188            let status = response.status();
189            let body = response.text().await.unwrap_or_default();
190            warn!("{} result: {}", status, body);
191            Err(EpicAPIError::HttpError { status, body })
192        }
193    }
194
195    /// Send an unauthenticated GET request and return the raw bytes
196    pub(crate) async fn get_bytes(&self, url: &str) -> Result<Vec<u8>, EpicAPIError> {
197        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
198        let response = self
199            .client
200            .get(parsed_url)
201            .send()
202            .await
203            .map_err(|e| {
204                error!("{:?}", e);
205                EpicAPIError::NetworkError(e)
206            })?;
207        if response.status() == reqwest::StatusCode::OK {
208            response.bytes().await.map(|b| b.to_vec()).map_err(|e| {
209                error!("{:?}", e);
210                EpicAPIError::DeserializationError(format!("{}", e))
211            })
212        } else {
213            let status = response.status();
214            let body = response.text().await.unwrap_or_default();
215            warn!("{} result: {}", status, body);
216            Err(EpicAPIError::HttpError { status, body })
217        }
218    }
219
220    #[allow(dead_code)]
221    /// Send an authorized DELETE request, returning Ok(()) on success
222    pub(crate) async fn authorized_delete(&self, url: &str) -> Result<(), EpicAPIError> {
223        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
224        let response = self
225            .set_authorization_header(self.client.delete(parsed_url))
226            .send()
227            .await
228            .map_err(|e| {
229                error!("{:?}", e);
230                EpicAPIError::NetworkError(e)
231            })?;
232        if response.status() == reqwest::StatusCode::OK
233            || response.status() == reqwest::StatusCode::NO_CONTENT
234        {
235            Ok(())
236        } else {
237            let status = response.status();
238            let body = response.text().await.unwrap_or_default();
239            warn!("{} result: {}", status, body);
240            Err(EpicAPIError::HttpError { status, body })
241        }
242    }
243}