steam-api 0.4.1

A crate for interacting with the steam api
Documentation
#![doc = include_str!("../README.md")]
#![deny(clippy::pedantic)]

pub mod functions;
pub mod structs;

use anyhow::Result;
use log::{debug, info};
use reqwest::{blocking, Error};
use structs::{bans, friends, level, profile, summaries};

/// Calls the `GetSteamLevel` API
///
/// # Returns
/// Returns an [`anyhow::Result`] containing [`structs::level::User`] and an [`anyhow::Error`]
///
/// # Arguments
/// * `steamids` - A string that contains a single Steam ID
/// * `api_key` - A string slice containing the API Key to use with the API.
///
/// # Errors
/// * [`reqwest::blocking::get`]
///
/// # Examples
/// Single Steam ID
/// ```
/// # fn main() -> anyhow::Result<()> {
/// let api_key = &std::env::var("API_KEY")?;
/// let steamid: &str = "76561198421169032";
///
/// let user = steam_api::get_steam_level(&steamid, &api_key)?;
///
/// match user.player_level {
///     Some(x) => println!("Player level\t{}", x),
///     None => println!("The profile is private or has not set up a community profile yet"),
/// }
///
/// # assert_eq!(user.player_level.unwrap_or_default(), 321);
/// # Ok(())
/// # }
/// ```
///
/// Multiple Steam IDs
/// `GetSteamLevel` does **NOT** support multiple steamids, blame Valve.
pub fn get_steam_level(steamids: &str, api_key: &str) -> Result<level::User, Error> {
    info!("Calling GetSteamLevel on steamid: {}", &steamids);

    debug!(
        "{}",
        functions::manage_api_url("GetSteamLevel", steamids, api_key)
    );

    Ok(blocking::get(functions::manage_api_url(
        "GetSteamLevel",
        steamids,
        api_key,
    ))?
    .json::<level::Response>()?
    .response)
}

/// Calls the `GetPlayerSummaries` API
///
/// # Returns
/// Returns an [`anyhow::Result`] containing a Vector of [`structs::summaries::User`] and [`anyhow::Error`]
///
/// # Arguments
/// * `steamids` - A string that contains either a single Steam ID or a comma separated string of
/// Steam IDs
/// * `api_key` - A string slice containing the API Key to use with the API.
///
/// # Errors
/// * [`reqwest::blocking::get`]
///
/// # Examples
/// Single Steam ID
/// ```
/// # fn main() -> anyhow::Result<()> {
/// let api_key = &std::env::var("API_KEY")?;
/// let steamid: &str = "76561198421169032";
///
/// let user = steam_api::get_player_summaries(&steamid, &api_key)?;
///
/// println!("Steam ID\t{:?}", user[0].steamid);
/// println!("Persona Name\t{:?}", user[0].personaname);
///
/// assert_eq!(user[0].personaname, "dind");
/// assert_eq!(user[0].profileurl, "https://steamcommunity.com/id/ageofconsent/");
/// # Ok(())
/// # }
/// ```
/// Multiple Steam IDs
/// ```
/// # fn main() -> anyhow::Result<()> {
/// let api_key = &std::env::var("API_KEY")?;
/// let steamids: &str = "76561198421169032,76561198421169032";
///
/// let users = steam_api::get_player_summaries(&steamids, &api_key)?;
///
/// for i in users {
///     println!("Persona Name\t{}", i.personaname);
/// }
/// # Ok(())
/// # }
/// ```
pub fn get_player_summaries(steamids: &str, api_key: &str) -> Result<Vec<summaries::User>, Error> {
    info!("Calling GetPlayerSummaries on steamids: {}", &steamids);

    debug!(
        "{}",
        functions::manage_api_url("GetPlayerSummaries", steamids, api_key)
    );

    Ok(blocking::get(functions::manage_api_url(
        "GetPlayerSummaries",
        steamids,
        api_key,
    ))?
    .json::<summaries::Response>()?
    .response
    .players)
}

/// Calls the `GetPlayerBans` API
///
/// # Returns
/// Returns an [`anyhow::Result`] containing a Vector of [`structs::bans::User`] and [`anyhow::Error`]
///
/// # Arguments
/// * `steamids` - A string that contains either a single Steam ID or a comma separated string of
/// Steam IDs
/// * `api_key` - A string slice containing the API Key to use with the API.
///
/// # Errors
/// * [`reqwest::blocking::get`]
///
/// # Examples
/// Single Steam ID
/// ```
/// # fn main() -> anyhow::Result<()> {
/// let api_key = &std::env::var("API_KEY")?;
/// let steamid: &str = "76561198421169032";
///
/// let user = steam_api::get_player_bans(&steamid, &api_key)?;
///
/// println!("{}", user[0].SteamId);
/// println!("{}", user[0].VACBanned);
///
/// assert_eq!(user[0].SteamId, "76561198421169032");
/// assert!(!user[0].VACBanned);
/// # Ok(())
/// # }
/// ```
/// Multiple Steam IDs
/// ```
/// # fn main() -> anyhow::Result<()> {
/// let api_key = &std::env::var("API_KEY")?;
/// let steamids: &str = "76561198421169032,76561198421169032";
///
/// let user = steam_api::get_player_bans(&steamids, &api_key)?;
///
/// for i in user {
///     println!("Steam ID\t{}", i.SteamId);
///     println!("VAC Banned\t{}", i.VACBanned);
/// }
/// # Ok(())
/// # }
/// ```
pub fn get_player_bans(steamids: &str, api_key: &str) -> Result<Vec<bans::User>, Error> {
    info!("Calling GetPlayerBans on steamids: {}", &steamids);

    debug!(
        "{}",
        functions::manage_api_url("GetPlayerBans", steamids, api_key)
    );

    Ok(blocking::get(functions::manage_api_url(
        "GetPlayerBans",
        steamids,
        api_key,
    ))?
    .json::<bans::Response>()?
    .players)
}

/// Calls the `GetFriendList` API
///
/// # Returns
/// Returns an [`anyhow::Result`] containing a Vector of [`structs::friends::User`] and [`anyhow::Error`]
///
/// # Arguments
/// * `steamids` - A string that contains a single Steam ID
/// * `api_key` - A string slice containing the API Key to use with the API.
///
/// # Errors
/// * [`reqwest::blocking::get`]
///
/// # Examples
/// Single Steam ID
/// ```
/// # fn main() -> anyhow::Result<()> {
/// let api_key = &std::env::var("API_KEY")?;
/// let steamid: &str = "76561198421169032";
///
/// let user = steam_api::get_friends_list(&steamid, &api_key)?;
///
/// for i in user {
///     println!("Steam ID\t{}", i.steamid);
///     println!("Friends since\t{}", i.friend_since);
/// }
/// # Ok(())
/// # }
/// ```
/// Multiple Steam IDs
///
/// `GetFriendList` does **NOT** support multiple steamids, blame Valve.
pub fn get_friends_list(steamids: &str, api_key: &str) -> Result<Vec<friends::User>, Error> {
    info!("Calling GetFriendList on steamid: {}", &steamids);

    debug!(
        "{}",
        functions::manage_api_url("GetFriendList", steamids, api_key)
    );

    Ok(blocking::get(functions::manage_api_url(
        "GetFriendList",
        steamids,
        api_key,
    ))?
    .json::<friends::Response>()?
    .friendslist
    .friends)
}

/// Calls all the APIs builds the [`structs::profile::Users`] struct
///
/// # Returns
/// Returns an `anyhow::Result` containing [`structs::profile::Users`] and [`anyhow::Error`]
///
/// # Arguments
/// * `steamids` - A Vector of `str`s
/// * `api_key` - A string slice containing the API Key to use with the API.
///
/// # Errors
/// * [`get_friends_list`]
/// * [`get_player_bans`]
/// * [`get_player_summaries`]
/// * [`get_steam_level`]
///
/// # Examples
/// Single Steam ID
/// ```
/// # fn main() -> anyhow::Result<()> {
/// let api_key = &std::env::var("API_KEY")?;
/// let steamids = vec![
///     "76561198421169032",
/// ];
///
/// let users = steam_api::get_profile_info(&steamids, &api_key)?;
///
/// for user in users.user {
///     println!("Persona Name\t{}", user.personaname);
///     println!("Steam Level\t{}", user.player_level);
///     println!("Profile URL\t{}", user.profileurl);
/// }
/// # Ok(())
/// # }
/// ```
/// Multiple Steam IDs
/// ```
/// # fn main() -> anyhow::Result<()> {
/// let api_key = &std::env::var("API_KEY")?;
/// let steamids = vec![
///     "76561198421169032",
///     "76561198421169032",
///     "76561198421169032",
///     "76561198421169032",
/// ];
///
/// let users = steam_api::get_profile_info(&steamids, &api_key)?;
///
/// for user in users.user {
///     println!("Persona Name\t{}", user.personaname);
///     println!("Steam Level\t{}", user.player_level);
///     println!("Profile URL\t{}", user.profileurl);
/// }
/// # Ok(())
/// # }
/// ```
pub fn get_profile_info(steamids: &[&str], api_key: &str) -> Result<profile::Users, Error> {
    // Initialize users struct
    let mut users: profile::Users = profile::Users::default();

    // we store the raw responses here
    let mut summaries_raw: summaries::Response = summaries::Response::default();
    let mut bans_raw: bans::Response = bans::Response::default();

    if steamids.len() > 32 {
        info!("Steamids are >32, splitting them into multiple api calls");
        // split the vector into chunks of 32
        let split_vec: Vec<&[&str]> = steamids.chunks(32).collect();

        // iterate over the chunks
        for i in split_vec {
            // combine chunk into nice sendable string
            let steamids_string = i.join(",");

            // get ban chunk info and push to raw_data vector
            let bans = get_player_bans(&steamids_string, api_key)?;
            users.api_call_count += 1;

            for i in bans {
                bans_raw.players.push(i);
            }

            // get summaries chunk info and push to raw_data vector
            let summaries = get_player_summaries(&steamids_string, api_key)?;
            users.api_call_count += 1;

            for i in summaries {
                summaries_raw.response.players.push(i);
            }
        }
    } else {
        let steamids_string = steamids.join(",");
        let bans = get_player_bans(&steamids_string, api_key)?;
        users.api_call_count += 1;
        for i in bans {
            bans_raw.players.push(i);
        }

        // get summaries chunk info and push to raw_data vector
        let summaries = get_player_summaries(&steamids_string, api_key)?;
        users.api_call_count += 1;
        for i in summaries {
            summaries_raw.response.players.push(i);
        }
    }

    // sort the vectors
    bans_raw.players.sort_by(|a, b| b.SteamId.cmp(&a.SteamId));
    summaries_raw
        .response
        .players
        .sort_by(|a, b| b.steamid.cmp(&a.steamid));

    // Null values will be 0 or ""
    // iterate over GetPlayerSummaries response
    for (index, player) in summaries_raw.response.players.iter().enumerate() {
        let mut user = profile::User {
            steamid: player.steamid.clone(),
            communityvisibilitystate: player.communityvisibilitystate,
            profilestate: player.profilestate,
            personaname: player.personaname.clone(),
            commentpermission: player.commentpermission.unwrap_or(0),
            profileurl: player.profileurl.clone(),
            avatar: player.avatar.clone(),
            avatarmedium: player.avatarmedium.clone(),
            avatarfull: player.avatarfull.clone(),
            avatarhash: player.avatarhash.clone(),
            lastlogoff: player.lastlogoff.unwrap_or(0),
            personastate: player.personastate,
            realname: player.realname.clone().unwrap_or_default(),
            primaryclanid: player.primaryclanid.clone().unwrap_or_default(),
            timecreated: player.timecreated.unwrap_or(0),
            gameid: player.gameid.clone().unwrap_or_default(),
            gameserverip: player.gameserverip.clone().unwrap_or_default(),
            loccountrycode: player.loccountrycode.clone().unwrap_or_default(),
            locstatecode: player.locstatecode.clone().unwrap_or_default(),
            loccityid: player.loccityid.unwrap_or(0),
            CommunityBanned: bans_raw.players[index].CommunityBanned,
            VACBanned: bans_raw.players[index].VACBanned,
            NumberOfVACBans: bans_raw.players[index].NumberOfVACBans,
            DaysSinceLastBan: bans_raw.players[index].DaysSinceLastBan,
            NumberOfGameBans: bans_raw.players[index].NumberOfGameBans,
            EconomyBan: bans_raw.players[index].EconomyBan.clone(),
            player_level: 0,
        };
        // If the profile is private, don't bother calling the api
        if user.communityvisibilitystate == 3 {
            user.player_level = get_steam_level(&player.steamid, api_key)?
                .player_level
                .unwrap_or_default();
            users.api_call_count += 1;
        } else {
            info!("{} is private, skipping GetSteamLevel", user.steamid);
        }
        users.user.push(user);
    }

    Ok(users)
}