Skip to main content

egs_api/api/
fab.rs

1use crate::api::error::EpicAPIError;
2use crate::api::types::download_manifest::DownloadManifest;
3use crate::api::types::fab_asset_manifest::DownloadInfo;
4use crate::api::types::fab_library::FabLibrary;
5use crate::api::EpicAPI;
6use log::{debug, error, warn};
7use std::borrow::BorrowMut;
8use url::Url;
9
10impl EpicAPI {
11    /// Fetch Fab asset manifest with signed distribution points. Returns `FabTimeout` on 403.
12    pub async fn fab_asset_manifest(
13        &self,
14        artifact_id: &str,
15        namespace: &str,
16        asset_id: &str,
17        platform: Option<&str>,
18    ) -> Result<Vec<DownloadInfo>, EpicAPIError> {
19        let url = format!("https://www.fab.com/e/artifacts/{}/manifest", artifact_id);
20        let parsed_url = Url::parse(&url).map_err(|_| EpicAPIError::InvalidParams)?;
21        match self
22            .authorized_post_client(parsed_url)
23            .json(&serde_json::json!({
24                "item_id": asset_id,
25                "namespace": namespace,
26                "platform": platform.unwrap_or("Windows"),
27            }))
28            .send()
29            .await
30        {
31            Ok(response) => {
32                if response.status() == reqwest::StatusCode::OK {
33                    let text = response.text().await.unwrap_or_default();
34                    match serde_json::from_str::<
35                        crate::api::types::fab_asset_manifest::FabAssetManifest,
36                    >(&text)
37                    {
38                        Ok(manifest) => Ok(manifest.download_info),
39                        Err(e) => {
40                            error!("{:?}", e);
41                            debug!("{}", text);
42                            Err(EpicAPIError::DeserializationError(format!("{}", e)))
43                        }
44                    }
45                } else if response.status() == reqwest::StatusCode::FORBIDDEN {
46                    Err(EpicAPIError::FabTimeout)
47                } else {
48                    debug!("{:?}", response.headers());
49                    let status = response.status();
50                    let body = response.text().await.unwrap_or_default();
51                    warn!("{} result: {}", status, body);
52                    Err(EpicAPIError::HttpError { status, body })
53                }
54            }
55            Err(e) => {
56                error!("{:?}", e);
57                Err(EpicAPIError::NetworkError(e))
58            }
59        }
60    }
61
62    /// Download and parse a Fab manifest from a distribution point.
63    pub async fn fab_download_manifest(
64        &self,
65        download_info: DownloadInfo,
66        distribution_point_url: &str,
67    ) -> Result<DownloadManifest, EpicAPIError> {
68        match download_info.get_distribution_point_by_base_url(distribution_point_url) {
69            None => {
70                error!("Distribution point not found");
71                Err(EpicAPIError::InvalidParams)
72            }
73            Some(point) => {
74                if point.signature_expiration < time::OffsetDateTime::now_utc() {
75                    error!("Expired signature");
76                    Err(EpicAPIError::InvalidParams)
77                } else {
78                    let data = self.get_bytes(&point.manifest_url).await?;
79                    match DownloadManifest::parse(data) {
80                        None => {
81                            error!("Unable to parse the Download Manifest");
82                            Err(EpicAPIError::DeserializationError(
83                                "Unable to parse the Download Manifest".to_string(),
84                            ))
85                        }
86                        Some(man) => Ok(man),
87                    }
88                }
89            }
90        }
91    }
92
93    /// Fetch all Fab library items, paginating internally.
94    pub async fn fab_library_items(
95        &mut self,
96        account_id: String,
97    ) -> Result<FabLibrary, EpicAPIError> {
98        let mut library = FabLibrary::default();
99
100        loop {
101            let url = match &library.cursors.next {
102                None => {
103                    format!(
104                        "https://www.fab.com/e/accounts/{}/ue/library?count=100",
105                        account_id
106                    )
107                }
108                Some(c) => {
109                    format!(
110                        "https://www.fab.com/e/accounts/{}/ue/library?cursor={}&count=100",
111                        account_id, c
112                    )
113                }
114            };
115
116            match self.authorized_get_json::<FabLibrary>(&url).await {
117                Ok(mut api_library) => {
118                    library.cursors.next = api_library.cursors.next;
119                    library.results.append(api_library.results.borrow_mut());
120                }
121                Err(e) => {
122                    error!("{:?}", e);
123                    library.cursors.next = None;
124                }
125            }
126            if library.cursors.next.is_none() {
127                break;
128            }
129        }
130
131        Ok(library)
132    }
133
134    /// Fetch download info for a specific file within a Fab listing.
135    pub async fn fab_file_download_info(
136        &self,
137        listing_id: &str,
138        format_id: &str,
139        file_id: &str,
140    ) -> Result<DownloadInfo, EpicAPIError> {
141        let url = format!(
142            "https://www.fab.com/p/egl/listings/{}/asset-formats/{}/files/{}/download-info",
143            listing_id, format_id, file_id
144        );
145        self.authorized_get_json(&url).await
146    }
147}