egs_api/api/
egs.rs

1use crate::api::error::EpicAPIError;
2use crate::api::types::asset_info::{AssetInfo, GameToken, OwnershipToken};
3use crate::api::types::asset_manifest::AssetManifest;
4use crate::api::types::download_manifest::DownloadManifest;
5use crate::api::types::epic_asset::EpicAsset;
6use crate::api::types::library::Library;
7use crate::api::EpicAPI;
8use log::{debug, error, warn};
9use std::borrow::BorrowMut;
10use std::collections::HashMap;
11use std::str::FromStr;
12use url::Url;
13
14impl EpicAPI {
15    pub async fn assets(
16        &mut self,
17        platform: Option<String>,
18        label: Option<String>,
19    ) -> Result<Vec<EpicAsset>, EpicAPIError> {
20        let plat = platform.unwrap_or_else(|| "Windows".to_string());
21        let lab = label.unwrap_or_else(|| "Live".to_string());
22        let url = format!("https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/{}?label={}", plat, lab);
23        match self
24            .authorized_get_client(Url::parse(&url).unwrap())
25            .send()
26            .await
27        {
28            Ok(response) => {
29                if response.status() == reqwest::StatusCode::OK {
30                    match response.json().await {
31                        Ok(assets) => Ok(assets),
32                        Err(e) => {
33                            error!("{:?}", e);
34                            Err(EpicAPIError::Unknown)
35                        }
36                    }
37                } else {
38                    warn!(
39                        "{} result: {}",
40                        response.status(),
41                        response.text().await.unwrap()
42                    );
43                    Err(EpicAPIError::Unknown)
44                }
45            }
46            Err(e) => {
47                error!("{:?}", e);
48                Err(EpicAPIError::Unknown)
49            }
50        }
51    }
52
53    pub async fn asset_manifest(
54        &self,
55        platform: Option<String>,
56        label: Option<String>,
57        namespace: Option<String>,
58        item_id: Option<String>,
59        app: Option<String>,
60    ) -> Result<AssetManifest, EpicAPIError> {
61        if namespace.is_none() {
62            return Err(EpicAPIError::InvalidParams);
63        };
64        if item_id.is_none() {
65            return Err(EpicAPIError::InvalidParams);
66        };
67        if app.is_none() {
68            return Err(EpicAPIError::InvalidParams);
69        };
70        let url = format!("https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/{}/namespace/{}/catalogItem/{}/app/{}/label/{}",
71                          platform.clone().unwrap_or_else(|| "Windows".to_string()), namespace.clone().unwrap(), item_id.clone().unwrap(), app.clone().unwrap(), label.clone().unwrap_or_else(|| "Live".to_string()));
72        match self
73            .authorized_get_client(Url::parse(&url).unwrap())
74            .send()
75            .await
76        {
77            Ok(response) => {
78                if response.status() == reqwest::StatusCode::OK {
79                    match response.json::<AssetManifest>().await {
80                        Ok(mut manifest) => {
81                            manifest.platform = platform;
82                            manifest.label = label;
83                            manifest.namespace = namespace;
84                            manifest.item_id = item_id;
85                            manifest.app = app;
86                            Ok(manifest)
87                        }
88                        Err(e) => {
89                            error!("{:?}", e);
90                            Err(EpicAPIError::Unknown)
91                        }
92                    }
93                } else {
94                    warn!(
95                        "{} result: {}",
96                        response.status(),
97                        response.text().await.unwrap()
98                    );
99                    Err(EpicAPIError::Unknown)
100                }
101            }
102            Err(e) => {
103                error!("{:?}", e);
104                Err(EpicAPIError::Unknown)
105            }
106        }
107    }
108
109    pub async fn asset_download_manifests(
110        &self,
111        asset_manifest: AssetManifest,
112    ) -> Vec<DownloadManifest> {
113        let base_urls = asset_manifest.url_csv();
114        let mut result: Vec<DownloadManifest> = Vec::new();
115        for elem in asset_manifest.elements {
116            for manifest in elem.manifests {
117                let mut queries: Vec<String> = Vec::new();
118                debug!("{:?}", manifest);
119                for query in manifest.query_params {
120                    queries.push(format!("{}={}", query.name, query.value));
121                }
122                let url = format!("{}?{}", manifest.uri, queries.join("&"));
123                let client = EpicAPI::build_client().build().unwrap();
124                match client.get(Url::from_str(&url).unwrap()).send().await {
125                    Ok(response) => {
126                        if response.status() == reqwest::StatusCode::OK {
127                            match response.bytes().await {
128                                Ok(data) => match DownloadManifest::parse(data.to_vec()) {
129                                    None => {
130                                        error!("Unable to parse the Download Manifest");
131                                    }
132                                    Some(mut man) => {
133                                        let mut url = manifest.uri.clone();
134                                        url.set_path(&match url.path_segments() {
135                                            None => "".to_string(),
136                                            Some(segments) => {
137                                                let mut vec: Vec<&str> = segments.collect();
138                                                vec.remove(vec.len() - 1);
139                                                vec.join("/")
140                                            }
141                                        });
142                                        url.set_query(None);
143                                        url.set_fragment(None);
144                                        man.set_custom_field(
145                                            "BaseUrl".to_string(),
146                                            base_urls.clone(),
147                                        );
148
149                                        if let Some(id) = asset_manifest.item_id.clone() {
150                                            man.set_custom_field(
151                                                "CatalogItemId".to_string(),
152                                                id.clone(),
153                                            );
154                                        }
155                                        if let Some(label) = asset_manifest.label.clone() {
156                                            man.set_custom_field(
157                                                "BuildLabel".to_string(),
158                                                label.clone(),
159                                            );
160                                        }
161                                        if let Some(ns) = asset_manifest.namespace.clone() {
162                                            man.set_custom_field(
163                                                "CatalogNamespace".to_string(),
164                                                ns.clone(),
165                                            );
166                                        }
167
168                                        if let Some(app) = asset_manifest.app.clone() {
169                                            man.set_custom_field(
170                                                "CatalogAssetName".to_string(),
171                                                app.clone(),
172                                            );
173                                        }
174
175                                        man.set_custom_field(
176                                            "SourceURL".to_string(),
177                                            url.to_string(),
178                                        );
179                                        result.push(man)
180                                    }
181                                },
182                                Err(e) => {
183                                    error!("{:?}", e);
184                                }
185                            }
186                        } else {
187                            warn!(
188                                "{} result: {}",
189                                response.status(),
190                                response.text().await.unwrap()
191                            );
192                        }
193                    }
194                    Err(e) => {
195                        error!("{:?}", e);
196                    }
197                }
198            }
199        }
200        result
201    }
202
203    pub async fn asset_info(
204        &self,
205        asset: EpicAsset,
206    ) -> Result<HashMap<String, AssetInfo>, EpicAPIError> {
207        let url = format!("https://catalog-public-service-prod06.ol.epicgames.com/catalog/api/shared/namespace/{}/bulk/items?id={}&includeDLCDetails=true&includeMainGameDetails=true&country=us&locale=lc",
208                          asset.namespace, asset.catalog_item_id);
209        match self
210            .authorized_get_client(Url::parse(&url).unwrap())
211            .send()
212            .await
213        {
214            Ok(response) => {
215                if response.status() == reqwest::StatusCode::OK {
216                    match response.json().await {
217                        Ok(info) => Ok(info),
218                        Err(e) => {
219                            error!("{:?}", e);
220                            Err(EpicAPIError::Unknown)
221                        }
222                    }
223                } else {
224                    warn!(
225                        "{} result: {}",
226                        response.status(),
227                        response.text().await.unwrap()
228                    );
229                    Err(EpicAPIError::Unknown)
230                }
231            }
232            Err(e) => {
233                error!("{:?}", e);
234                Err(EpicAPIError::Unknown)
235            }
236        }
237    }
238
239    pub async fn game_token(&self) -> Result<GameToken, EpicAPIError> {
240        let url =
241            "https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/exchange"
242                .to_string();
243        match self
244            .authorized_get_client(Url::parse(&url).unwrap())
245            .send()
246            .await
247        {
248            Ok(response) => {
249                if response.status() == reqwest::StatusCode::OK {
250                    match response.json().await {
251                        Ok(token) => Ok(token),
252                        Err(e) => {
253                            error!("{:?}", e);
254                            Err(EpicAPIError::Unknown)
255                        }
256                    }
257                } else {
258                    warn!(
259                        "{} result: {}",
260                        response.status(),
261                        response.text().await.unwrap()
262                    );
263                    Err(EpicAPIError::Unknown)
264                }
265            }
266            Err(e) => {
267                error!("{:?}", e);
268                Err(EpicAPIError::Unknown)
269            }
270        }
271    }
272
273    pub async fn ownership_token(&self, asset: EpicAsset) -> Result<OwnershipToken, EpicAPIError> {
274        let url = match &self.user_data.account_id {
275            None => {
276                return Err(EpicAPIError::InvalidCredentials);
277            }
278            Some(id) => {
279                format!("https://ecommerceintegration-public-service-ecomprod02.ol.epicgames.com/ecommerceintegration/api/public/platforms/EPIC/identities/{}/ownershipToken",
280                        id)
281            }
282        };
283        match self
284            .authorized_post_client(Url::parse(&url).unwrap())
285            .form(&[(
286                "nsCatalogItemId".to_string(),
287                format!("{}:{}", asset.namespace, asset.catalog_item_id),
288            )])
289            .send()
290            .await
291        {
292            Ok(response) => {
293                if response.status() == reqwest::StatusCode::OK {
294                    match response.json().await {
295                        Ok(token) => Ok(token),
296                        Err(e) => {
297                            error!("{:?}", e);
298                            Err(EpicAPIError::Unknown)
299                        }
300                    }
301                } else {
302                    warn!(
303                        "{} result: {}",
304                        response.status(),
305                        response.text().await.unwrap()
306                    );
307                    Err(EpicAPIError::Unknown)
308                }
309            }
310            Err(e) => {
311                error!("{:?}", e);
312                Err(EpicAPIError::Unknown)
313            }
314        }
315    }
316
317    pub async fn library_items(&mut self, include_metadata: bool) -> Result<Library, EpicAPIError> {
318        let mut library = Library {
319            records: vec![],
320            response_metadata: Default::default(),
321        };
322        let mut cursor: Option<String> = None;
323        loop {
324            let url = match &cursor {
325                None => {
326                    format!("https://library-service.live.use1a.on.epicgames.com/library/api/public/items?includeMetadata={}", include_metadata)
327                }
328                Some(c) => {
329                    format!("https://library-service.live.use1a.on.epicgames.com/library/api/public/items?includeMetadata={}&cursor={}", include_metadata, c)
330                }
331            };
332
333            match self
334                .authorized_get_client(Url::parse(&url).unwrap())
335                .send()
336                .await
337            {
338                Ok(response) => {
339                    if response.status() == reqwest::StatusCode::OK {
340                        match response.json::<Library>().await {
341                            Ok(mut records) => {
342                                library.records.append(records.records.borrow_mut());
343                                match records.response_metadata {
344                                    None => {
345                                        break;
346                                    }
347                                    Some(meta) => match meta.next_cursor {
348                                        None => {
349                                            break;
350                                        }
351                                        Some(curs) => {
352                                            cursor = Some(curs);
353                                        }
354                                    },
355                                }
356                            }
357                            Err(e) => {
358                                error!("{:?}", e);
359                            }
360                        }
361                    } else {
362                        warn!(
363                            "{} result: {}",
364                            response.status(),
365                            response.text().await.unwrap()
366                        );
367                    }
368                }
369                Err(e) => {
370                    error!("{:?}", e);
371                }
372            };
373            if cursor.is_none() {
374                break;
375            }
376        }
377        Ok(library)
378    }
379}