use std::borrow::BorrowMut;
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use chrono::{DateTime, Utc};
use reqwest::{Client, RequestBuilder, Response};
use reqwest::header::HeaderMap;
use serde::{Deserialize, Serialize};
use types::{AssetInfo, AssetManifest, DownloadManifest, Entitlement, EpicAsset, GameToken, Library, Manifest, OwnershipToken};
pub mod types;
#[allow(missing_docs)]
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UserData {
pub access_token: Option<String>,
pub expires_in: Option<i64>,
pub expires_at: Option<DateTime<Utc>>,
pub token_type: Option<String>,
pub refresh_token: Option<String>,
pub refresh_expires: Option<i64>,
pub refresh_expires_at: Option<DateTime<Utc>>,
pub account_id: Option<String>,
pub client_id: Option<String>,
pub internal_client: Option<bool>,
pub client_service: Option<String>,
#[serde(rename = "displayName")]
pub display_name: Option<String>,
pub app: Option<String>,
pub in_app_id: Option<String>,
pub device_id: Option<String>,
#[serde(rename = "errorMessage")]
pub error_message: Option<String>,
#[serde(rename = "errorCode")]
pub error_code: Option<String>,
}
impl UserData {
pub fn update(&mut self, new: UserData) {
if let Some(n) = new.access_token { self.access_token = Some(n) }
if let Some(n) = new.expires_in { self.expires_in = Some(n) }
if let Some(n) = new.expires_at { self.expires_at = Some(n) }
if let Some(n) = new.token_type { self.token_type = Some(n) }
if let Some(n) = new.refresh_token { self.refresh_token = Some(n) }
if let Some(n) = new.refresh_expires { self.refresh_expires = Some(n) }
if let Some(n) = new.refresh_expires_at { self.refresh_expires_at = Some(n) }
if let Some(n) = new.account_id { self.account_id = Some(n) }
if let Some(n) = new.client_id { self.client_id = Some(n) }
if let Some(n) = new.internal_client { self.internal_client = Some(n) }
if let Some(n) = new.client_service { self.client_service = Some(n) }
if let Some(n) = new.display_name { self.display_name = Some(n) }
if let Some(n) = new.app { self.app = Some(n) }
if let Some(n) = new.in_app_id { self.in_app_id = Some(n) }
if let Some(n) = new.device_id { self.device_id = Some(n) }
if let Some(n) = new.error_message { self.error_message = Some(n) }
if let Some(n) = new.error_code { self.error_code = Some(n) }
}
}
pub(crate) struct EpicAPI {
client: Client,
pub(crate) user_data: UserData,
}
#[derive(Debug)]
pub enum EpicAPIError {
InvalidCredentials,
APIError(String),
Unknown,
Server,
}
impl fmt::Display for EpicAPIError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &*self {
EpicAPIError::InvalidCredentials => { write!(f, "Invalid Credentials") }
EpicAPIError::Unknown => { write!(f, "Unknown Error") }
EpicAPIError::Server => { write!(f, "Server Error") }
EpicAPIError::APIError(e) => { write!(f, "API Error: {}", e) }
}
}
}
impl Error for EpicAPIError {
fn description(&self) -> &str {
match *self {
EpicAPIError::InvalidCredentials => { "Invalid Credentials" }
EpicAPIError::Unknown => { "Unknown Error" }
EpicAPIError::Server => { "Server Error" }
EpicAPIError::APIError(_) => { "API Error" }
}
}
}
impl EpicAPI {
pub fn new() -> Self {
let mut headers = HeaderMap::new();
headers.insert("User-Agent", "UELauncher/12.0.5-15338009+++Portal+Release-Live Windows/6.1.7601.1.0.64bit".parse().unwrap());
let client = reqwest::Client::builder()
.default_headers(headers)
.cookie_store(true).build().unwrap();
EpicAPI { client, user_data: Default::default() }
}
pub async fn start_session(&mut self, refresh_token: Option<String>, exchange_token: Option<String>) -> Result<bool, EpicAPIError> {
let params = match refresh_token {
None => {
match exchange_token {
None => { return Err(EpicAPIError::InvalidCredentials); }
Some(exchange) => { [("grant_type".to_string(), "exchange_code".to_string()), ("exchange_code".to_string(), exchange), ("token_type".to_string(), "eg1".to_string())] }
}
}
Some(refresh) => { [("grant_type".to_string(), "refresh_token".to_string()), ("refresh_token".to_string(), refresh), ("token_type".to_string(), "eg1".to_string())] }
};
match self.client
.post("https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token")
.form(¶ms)
.basic_auth("34a02cf8f4414e29b15921876da36f9a", Some("daafbccc737745039dffe53d94fc76cf"))
.send().await {
Ok(response) => {
return self.handle_login_response(response).await;
}
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
}
async fn handle_login_response(&mut self, response: Response) -> Result<bool, EpicAPIError> {
if response.status() == reqwest::StatusCode::INTERNAL_SERVER_ERROR {
println!("Server Error");
return Err(EpicAPIError::Server);
}
let new: UserData = match response.json().await {
Ok(data) => data,
Err(e) => {
println!("Error: {:?}", e);
return Err(EpicAPIError::Unknown);
}
};
self.user_data.update(new);
match &self.user_data.error_message {
None => {}
Some(m) => {
println!("Error: {}", m);
return Err(EpicAPIError::APIError(m.to_string()));
}
}
Ok(true)
}
fn get_authorized_get_client(&self, url: &str) -> RequestBuilder {
self.set_authorization_header(self.client.get(url))
}
fn get_authorized_post_client(&self, url: &str) -> RequestBuilder {
self.set_authorization_header(self.client.post(url))
}
fn set_authorization_header(&self, rb: RequestBuilder) -> RequestBuilder {
rb.header("Authorization", format!("{} {}", self.user_data.token_type.as_ref().unwrap_or(&"bearer".to_string()), self.user_data.access_token.as_ref().unwrap_or(&"".to_string())))
}
pub async fn resume_session(&mut self) -> Result<bool, EpicAPIError> {
match self.get_authorized_get_client("https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/verify")
.send()
.await {
Ok(response) => {
return self.handle_login_response(response).await;
}
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
}
pub async fn get_assets(&mut self, platform: Option<String>, label: Option<String>) -> Result<Vec<EpicAsset>, EpicAPIError> {
let plat = platform.unwrap_or("Windows".to_string());
let lab = label.unwrap_or("Live".to_string());
let url = format!("https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/{}?label={}", plat, lab);
match self.get_authorized_get_client(&url)
.send()
.await {
Ok(response) => {
if response.status() == reqwest::StatusCode::OK {
match response.json().await {
Ok(assets) => Ok(assets),
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
} else {
println!("{} result: {}", response.status(), response.text().await.unwrap());
Err(EpicAPIError::Unknown)
}
}
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
}
pub async fn get_asset_manifest(&self, platform: Option<String>, label: Option<String>, asset: EpicAsset) -> Result<AssetManifest, EpicAPIError> {
let url = format!("https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/{}/namespace/{}/catalogItem/{}/app/{}/label/{}",
platform.unwrap_or("Windows".to_string()), asset.namespace, asset.catalog_item_id, asset.app_name, label.unwrap_or("Live".to_string()));
match self.get_authorized_get_client(&url).send().await {
Ok(response) => {
if response.status() == reqwest::StatusCode::OK {
match response.json().await {
Ok(manifest) => { Ok(manifest) }
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
} else {
println!("{} result: {}", response.status(), response.text().await.unwrap());
Err(EpicAPIError::Unknown)
}
}
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
}
pub async fn get_asset_download_manifest(&self, manifest: Manifest) -> Result<DownloadManifest, EpicAPIError> {
let mut queries: Vec<(String, String)> = Vec::new();
for query in manifest.query_params {
queries.push((query.name, query.value));
}
match self.get_authorized_get_client(&manifest.uri)
.query(&queries)
.send().await {
Ok(response) => {
if response.status() == reqwest::StatusCode::OK {
match response.json().await {
Ok(manifest) => { Ok(manifest) }
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
} else {
println!("{} result: {}", response.status(), response.text().await.unwrap());
Err(EpicAPIError::Unknown)
}
}
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
}
pub async fn get_asset_info(&self, asset: EpicAsset) -> Result<HashMap<String, AssetInfo>, EpicAPIError> {
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",
asset.namespace, asset.catalog_item_id);
match self.get_authorized_get_client(&url).send().await {
Ok(response) => {
if response.status() == reqwest::StatusCode::OK {
match response.json().await {
Ok(info) => { Ok(info) }
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
} else {
println!("{} result: {}", response.status(), response.text().await.unwrap());
Err(EpicAPIError::Unknown)
}
}
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
}
pub async fn get_game_token(&self) -> Result<GameToken, EpicAPIError> {
let url = format!("https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/exchange");
match self.get_authorized_get_client(&url).send().await {
Ok(response) => {
if response.status() == reqwest::StatusCode::OK {
match response.json().await {
Ok(token) => { Ok(token) }
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
} else {
println!("{} result: {}", response.status(), response.text().await.unwrap());
Err(EpicAPIError::Unknown)
}
}
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
}
pub async fn get_ownership_token(&self, asset: EpicAsset) -> Result<OwnershipToken, EpicAPIError> {
let url = match &self.user_data.account_id {
None => { return Err(EpicAPIError::InvalidCredentials); }
Some(id) => {
format!("https://ecommerceintegration-public-service-ecomprod02.ol.epicgames.com/ecommerceintegration/api/public/platforms/EPIC/identities/{}/ownershipToken",
id)
}
};
match self.get_authorized_post_client(&url)
.form(&[("nsCatalogItemId".to_string(), format!("{}:{}", asset.namespace, asset.catalog_item_id))])
.send().await {
Ok(response) => {
if response.status() == reqwest::StatusCode::OK {
match response.json().await {
Ok(token) => { Ok(token) }
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
} else {
println!("{} result: {}", response.status(), response.text().await.unwrap());
Err(EpicAPIError::Unknown)
}
}
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
}
pub async fn get_user_entitlements(&self) -> Result<Vec<Entitlement>, EpicAPIError> {
let url = match &self.user_data.account_id {
None => { return Err(EpicAPIError::InvalidCredentials); }
Some(id) => {
format!("https://entitlement-public-service-prod08.ol.epicgames.com/entitlement/api/account/{}/entitlements?start=0&count=5000",
id)
}
};
match self.get_authorized_get_client(&url).send().await {
Ok(response) => {
if response.status() == reqwest::StatusCode::OK {
match response.json().await {
Ok(ent) => { Ok(ent) }
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
} else {
println!("{} result: {}", response.status(), response.text().await.unwrap());
Err(EpicAPIError::Unknown)
}
}
Err(e) => {
println!("Error: {:?}", e);
Err(EpicAPIError::Unknown)
}
}
}
pub async fn get_library_items(&mut self, include_metadata: bool) -> Result<Library, EpicAPIError> {
let mut library = Library { records: vec![], response_metadata: Default::default() };
let mut cursor: Option<String> = None;
loop {
let url = match &cursor {
None => {
format!("https://library-service.live.use1a.on.epicgames.com/library/api/public/items?includeMetadata={}", include_metadata)
}
Some(c) => {
format!("https://library-service.live.use1a.on.epicgames.com/library/api/public/items?includeMetadata={}&cursor={}", include_metadata, c)
}
};
match self.get_authorized_get_client(&url)
.send()
.await {
Ok(response) => {
if response.status() == reqwest::StatusCode::OK {
match response.json::<Library>().await {
Ok(mut records) => {
library.records.append(records.records.borrow_mut());
match records.response_metadata {
None => { break; }
Some(meta) => {
match meta.next_cursor {
None => { break; }
Some(curs) => {
cursor = Some(curs);
}
}
}
}
}
Err(e) => {
println!("Error: {:?}", e);
}
}
} else {
println!("{} result: {}", response.status(), response.text().await.unwrap());
}
}
Err(e) => {
println!("Error: {:?}", e);
}
};
if cursor.is_none() {
break;
}
};
Ok(library)
}
}