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#[allow(dead_code)]
30/// Cloud Save Methods
31pub mod cloud_save;
32/// EGS Methods
33pub mod egs;
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    async fn send(request: RequestBuilder) -> Result<reqwest::Response, EpicAPIError> {
114        request.send().await.map_err(|e| {
115            error!("{:?}", e);
116            EpicAPIError::NetworkError(e)
117        })
118    }
119
120    fn require_ok(response: &reqwest::Response) -> Result<(), ()> {
121        if response.status() == reqwest::StatusCode::OK {
122            Ok(())
123        } else {
124            Err(())
125        }
126    }
127
128    async fn error_response(response: reqwest::Response) -> EpicAPIError {
129        let status = response.status();
130        let body = response.text().await.unwrap_or_default();
131        warn!("{} result: {}", status, body);
132        EpicAPIError::HttpError { status, body }
133    }
134
135    async fn read_json<T: DeserializeOwned>(
136        response: reqwest::Response,
137        url: &str,
138    ) -> Result<T, EpicAPIError> {
139        let body = response.text().await.map_err(|e| {
140            error!("Failed to read response body from {}: {:?}", url, e);
141            EpicAPIError::DeserializationError(format!("{}", e))
142        })?;
143        serde_json::from_str::<T>(&body).map_err(|e| {
144            error!("Deserialization failed for {}: {:?}", url, e);
145            error!("Response body: {}", &body[..body.len().min(2048)]);
146            EpicAPIError::DeserializationError(format!("{}", e))
147        })
148    }
149
150    async fn send_and_deserialize<T: DeserializeOwned>(
151        request: RequestBuilder,
152        url: &str,
153    ) -> Result<T, EpicAPIError> {
154        let response = Self::send(request).await?;
155        if Self::require_ok(&response).is_ok() {
156            Self::read_json(response, url).await
157        } else {
158            Err(Self::error_response(response).await)
159        }
160    }
161
162    pub(crate) async fn authorized_get_json<T: DeserializeOwned>(
163        &self,
164        url: &str,
165    ) -> Result<T, EpicAPIError> {
166        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
167        debug!("authorized_get_json: {}", url);
168        Self::send_and_deserialize(self.authorized_get_client(parsed_url), url).await
169    }
170
171    pub(crate) async fn authorized_post_form_json<T: DeserializeOwned>(
172        &self,
173        url: &str,
174        form: &[(String, String)],
175    ) -> Result<T, EpicAPIError> {
176        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
177        debug!("authorized_post_form_json: {}", url);
178        Self::send_and_deserialize(self.authorized_post_client(parsed_url).form(form), url).await
179    }
180
181    pub(crate) async fn authorized_post_json<T: DeserializeOwned, B: serde::Serialize>(
182        &self,
183        url: &str,
184        body: &B,
185    ) -> Result<T, EpicAPIError> {
186        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
187        debug!("authorized_post_json: {}", url);
188        Self::send_and_deserialize(self.authorized_post_client(parsed_url).json(body), url).await
189    }
190
191    pub(crate) async fn get_bytes(&self, url: &str) -> Result<Vec<u8>, EpicAPIError> {
192        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
193        let response = Self::send(self.client.get(parsed_url)).await?;
194        if Self::require_ok(&response).is_ok() {
195            response.bytes().await.map(|b| b.to_vec()).map_err(|e| {
196                error!("{:?}", e);
197                EpicAPIError::DeserializationError(format!("{}", e))
198            })
199        } else {
200            Err(Self::error_response(response).await)
201        }
202    }
203
204    pub(crate) async fn get_json<T: DeserializeOwned>(&self, url: &str) -> Result<T, EpicAPIError> {
205        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
206        debug!("get_json: {}", url);
207        Self::send_and_deserialize(self.client.get(parsed_url), url).await
208    }
209
210    #[allow(dead_code)]
211    pub(crate) async fn authorized_delete(&self, url: &str) -> Result<(), EpicAPIError> {
212        let parsed_url = Url::parse(url).map_err(|_| EpicAPIError::InvalidParams)?;
213        let response =
214            Self::send(self.set_authorization_header(self.client.delete(parsed_url))).await?;
215        if response.status() == reqwest::StatusCode::OK
216            || response.status() == reqwest::StatusCode::NO_CONTENT
217        {
218            Ok(())
219        } else {
220            Err(Self::error_response(response).await)
221        }
222    }
223}