Skip to main content

egs_api/api/
store.rs

1use crate::api::error::EpicAPIError;
2use crate::api::types::uplay::{
3    UplayClaimResult, UplayCodesResult, UplayGraphQLResponse, UplayRedeemResult,
4};
5use crate::api::EpicAPI;
6use log::error;
7
8const UPLAY_CODES_QUERY: &str = r#"
9query partnerIntegrationQuery($accountId: String!) {
10  PartnerIntegration {
11    accountUplayCodes(accountId: $accountId) {
12      epicAccountId
13      gameId
14      uplayAccountId
15      regionCode
16      redeemedOnUplay
17      redemptionTimestamp
18    }
19  }
20}
21"#;
22
23const UPLAY_CLAIM_QUERY: &str = r#"
24mutation claimUplayCode($accountId: String!, $uplayAccountId: String!, $gameId: String!) {
25  PartnerIntegration {
26    claimUplayCode(
27      accountId: $accountId
28      uplayAccountId: $uplayAccountId
29      gameId: $gameId
30    ) {
31      data {
32        assignmentTimestam
33        epicAccountId
34        epicEntitlement {
35          entitlementId
36          catalogItemId
37          entitlementName
38          country
39        }
40        gameId
41        redeemedOnUplay
42        redemptionTimestamp
43        regionCode
44        uplayAccountId
45      }
46      success
47    }
48  }
49}
50"#;
51
52const UPLAY_REDEEM_QUERY: &str = r#"
53mutation redeemAllPendingCodes($accountId: String!, $uplayAccountId: String!) {
54  PartnerIntegration {
55    redeemAllPendingCodes(accountId: $accountId, uplayAccountId: $uplayAccountId) {
56      data {
57        epicAccountId
58        uplayAccountId
59        redeemedOnUplay
60        redemptionTimestamp
61      }
62      success
63    }
64  }
65}
66"#;
67
68const STORE_GQL_URL: &str = "https://launcher.store.epicgames.com/graphql";
69const STORE_USER_AGENT: &str = "EpicGamesLauncher/14.0.8-22004686+++Portal+Release-Live";
70
71impl EpicAPI {
72    /// Fetch Uplay codes linked to the user's Epic account via GraphQL.
73    pub async fn store_get_uplay_codes(
74        &self,
75    ) -> Result<UplayGraphQLResponse<UplayCodesResult>, EpicAPIError> {
76        let user_id = self
77            .user_data
78            .account_id
79            .as_deref()
80            .ok_or(EpicAPIError::InvalidCredentials)?;
81        let body = serde_json::json!({
82            "query": UPLAY_CODES_QUERY,
83            "variables": { "accountId": user_id },
84        });
85        self.store_gql_request(&body).await
86    }
87
88    /// Claim a Uplay code for a specific game.
89    pub async fn store_claim_uplay_code(
90        &self,
91        uplay_account_id: &str,
92        game_id: &str,
93    ) -> Result<UplayGraphQLResponse<UplayClaimResult>, EpicAPIError> {
94        let user_id = self
95            .user_data
96            .account_id
97            .as_deref()
98            .ok_or(EpicAPIError::InvalidCredentials)?;
99        let body = serde_json::json!({
100            "query": UPLAY_CLAIM_QUERY,
101            "variables": {
102                "accountId": user_id,
103                "uplayAccountId": uplay_account_id,
104                "gameId": game_id,
105            },
106        });
107        self.store_gql_request(&body).await
108    }
109
110    /// Redeem all pending Uplay codes for the user's account.
111    pub async fn store_redeem_uplay_codes(
112        &self,
113        uplay_account_id: &str,
114    ) -> Result<UplayGraphQLResponse<UplayRedeemResult>, EpicAPIError> {
115        let user_id = self
116            .user_data
117            .account_id
118            .as_deref()
119            .ok_or(EpicAPIError::InvalidCredentials)?;
120        let body = serde_json::json!({
121            "query": UPLAY_REDEEM_QUERY,
122            "variables": {
123                "accountId": user_id,
124                "uplayAccountId": uplay_account_id,
125            },
126        });
127        self.store_gql_request(&body).await
128    }
129
130    /// Internal helper for store GraphQL requests with the required User-Agent.
131    async fn store_gql_request<T: serde::de::DeserializeOwned>(
132        &self,
133        body: &serde_json::Value,
134    ) -> Result<T, EpicAPIError> {
135        let parsed_url =
136            url::Url::parse(STORE_GQL_URL).map_err(|_| EpicAPIError::InvalidParams)?;
137        let response = self
138            .set_authorization_header(self.client.post(parsed_url))
139            .header("User-Agent", STORE_USER_AGENT)
140            .json(body)
141            .send()
142            .await
143            .map_err(|e| {
144                error!("{:?}", e);
145                EpicAPIError::NetworkError(e)
146            })?;
147        if response.status() == reqwest::StatusCode::OK {
148            response.json::<T>().await.map_err(|e| {
149                error!("{:?}", e);
150                EpicAPIError::DeserializationError(format!("{}", e))
151            })
152        } else {
153            let status = response.status();
154            let body = response.text().await.unwrap_or_default();
155            log::warn!("{} result: {}", status, body);
156            Err(EpicAPIError::HttpError { status, body })
157        }
158    }
159}