steam-user 0.1.0

Steam User web client for Rust - HTTP-based Steam Community interactions
Documentation
use base64::Engine;
use prost::Message;
use steam_protos::messages::loyalty_rewards::{CLoyaltyRewardsRedeemPointsRequest, CLoyaltyRewardsRedeemPointsResponse};

use crate::{client::SteamUser, endpoint::steam_endpoint, error::SteamUserError};

impl SteamUser {
    /// Adds a free license for a given package ID to the user's Steam account.
    ///
    /// # Arguments
    ///
    /// * `package_id` - The package ID (not app ID) to add as a free license.
    ///
    /// # Returns
    ///
    /// Returns `Ok(true)` if the license was successfully added.
    #[steam_endpoint(POST, host = Store, path = "/freelicense/addfreelicense/{package_id}", kind = Write)]
    pub async fn add_free_license(&self, package_id: u32) -> Result<bool, SteamUserError> {
        let response = self.post_path(format!("/freelicense/addfreelicense/{}", package_id)).form(&[("ajax", "true")]).send().await?;

        let status = response.status();
        let data: serde_json::Value = response.json().await?;

        // JS logic: result?.status === 200 && Array.isArray(data) && !data.length;
        Ok(status.is_success() && data.is_array() && data.as_array().map(|a| a.is_empty()).unwrap_or(false))
    }

    /// Adds a free subscription license for a given sub ID to the user's Steam
    /// account.
    ///
    /// # Arguments
    ///
    /// * `sub_id` - The subscription ID to add as a free license.
    #[steam_endpoint(POST, host = Store, path = "/checkout/addfreelicense/", kind = Write)]
    pub async fn add_sub_free_license(&self, sub_id: u32) -> Result<bool, SteamUserError> {
        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?;

        let data = response.text().await?;
        if data.contains("There was a problem adding this product to your Steam account.") {
            return Ok(false);
        }

        Ok(true)
    }

    /// Redeems Steam Points for a specified loyalty reward (backgrounds,
    /// emojis, etc.).
    ///
    /// # Arguments
    ///
    /// * `definition_id` - The definition ID of the reward item in the Points
    ///   Shop.
    #[steam_endpoint(POST, host = Api, path = "/ILoyaltyRewardsService/RedeemPoints/v1", kind = Write)]
    pub async fn redeem_points(&self, definition_id: u32) -> Result<CLoyaltyRewardsRedeemPointsResponse, SteamUserError> {
        let access_token = self.session.access_token.as_ref().ok_or_else(|| SteamUserError::Other("Access token is required for redeem_points".into()))?;

        let request = CLoyaltyRewardsRedeemPointsRequest { defid: Some(definition_id), expected_points_cost: Some(0) };

        let mut body = Vec::new();
        request.encode(&mut body).map_err(|e| SteamUserError::Other(e.to_string()))?;

        let params = [("access_token", access_token.as_str()), ("spoof_steamid", ""), ("origin", "https://store.steampowered.com")];

        let response = self.post_path("/ILoyaltyRewardsService/RedeemPoints/v1").query(&params).form(&[("input_protobuf_encoded", base64::engine::general_purpose::STANDARD.encode(body))]).send().await?;

        if !response.status().is_success() {
            return Err(SteamUserError::Other(format!("HTTP error {}", response.status())));
        }

        let bytes = response.bytes().await?;
        let result = CLoyaltyRewardsRedeemPointsResponse::decode(bytes).map_err(|e| SteamUserError::Other(e.to_string()))?;

        Ok(result)
    }
}