Skip to main content

steam_user/services/
license.rs

1use base64::Engine;
2use prost::Message;
3use steam_protos::messages::loyalty_rewards::{CLoyaltyRewardsRedeemPointsRequest, CLoyaltyRewardsRedeemPointsResponse};
4
5use crate::{client::SteamUser, endpoint::steam_endpoint, error::SteamUserError};
6
7impl SteamUser {
8    /// Adds a free license for a given package ID to the user's Steam account.
9    ///
10    /// # Arguments
11    ///
12    /// * `package_id` - The package ID (not app ID) to add as a free license.
13    ///
14    /// # Returns
15    ///
16    /// Returns `Ok(true)` if the license was successfully added.
17    #[steam_endpoint(POST, host = Store, path = "/freelicense/addfreelicense/{package_id}", kind = Write)]
18    pub async fn add_free_license(&self, package_id: u32) -> Result<bool, SteamUserError> {
19        let response = self.post_path(format!("/freelicense/addfreelicense/{}", package_id)).form(&[("ajax", "true")]).send().await?;
20
21        let status = response.status();
22        let data: serde_json::Value = response.json().await?;
23
24        // JS logic: result?.status === 200 && Array.isArray(data) && !data.length;
25        Ok(status.is_success() && data.is_array() && data.as_array().map(|a| a.is_empty()).unwrap_or(false))
26    }
27
28    /// Adds a free subscription license for a given sub ID to the user's Steam
29    /// account.
30    ///
31    /// # Arguments
32    ///
33    /// * `sub_id` - The subscription ID to add as a free license.
34    #[steam_endpoint(POST, host = Store, path = "/checkout/addfreelicense/", kind = Write)]
35    pub async fn add_sub_free_license(&self, sub_id: u32) -> Result<bool, SteamUserError> {
36        let response = self.post_path("/checkout/addfreelicense/").form(&[("snr", "1_5_9__403"), ("originating_snr", "1_store-navigation__"), ("action", "add_to_cart"), ("subid", &sub_id.to_string())]).send().await?;
37
38        let data = response.text().await?;
39        if data.contains("There was a problem adding this product to your Steam account.") {
40            return Ok(false);
41        }
42
43        Ok(true)
44    }
45
46    /// Redeems Steam Points for a specified loyalty reward (backgrounds,
47    /// emojis, etc.).
48    ///
49    /// # Arguments
50    ///
51    /// * `definition_id` - The definition ID of the reward item in the Points
52    ///   Shop.
53    #[steam_endpoint(POST, host = Api, path = "/ILoyaltyRewardsService/RedeemPoints/v1", kind = Write)]
54    pub async fn redeem_points(&self, definition_id: u32) -> Result<CLoyaltyRewardsRedeemPointsResponse, SteamUserError> {
55        let access_token = self.session.access_token.as_ref().ok_or_else(|| SteamUserError::Other("Access token is required for redeem_points".into()))?;
56
57        let request = CLoyaltyRewardsRedeemPointsRequest { defid: Some(definition_id), expected_points_cost: Some(0) };
58
59        let mut body = Vec::new();
60        request.encode(&mut body).map_err(|e| SteamUserError::Other(e.to_string()))?;
61
62        let params = [("access_token", access_token.as_str()), ("spoof_steamid", ""), ("origin", "https://store.steampowered.com")];
63
64        let response = self.post_path("/ILoyaltyRewardsService/RedeemPoints/v1").query(&params).form(&[("input_protobuf_encoded", base64::engine::general_purpose::STANDARD.encode(body))]).send().await?;
65
66        if !response.status().is_success() {
67            return Err(SteamUserError::Other(format!("HTTP error {}", response.status())));
68        }
69
70        let bytes = response.bytes().await?;
71        let result = CLoyaltyRewardsRedeemPointsResponse::decode(bytes).map_err(|e| SteamUserError::Other(e.to_string()))?;
72
73        Ok(result)
74    }
75}