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}