Skip to main content

steam_user/types/
profile.rs

1//! Profile settings types.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use steamid::SteamID;
6
7/// Privacy state for profile elements.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[non_exhaustive]
10#[repr(i32)]
11#[derive(Default)]
12pub enum PrivacyState {
13    /// Only visible to the user.
14    Private = 1,
15    /// Visible to friends only.
16    FriendsOnly = 2,
17    /// Visible to everyone.
18    #[default]
19    Public = 3,
20}
21
22impl From<i32> for PrivacyState {
23    fn from(value: i32) -> Self {
24        match value {
25            1 => Self::Private,
26            2 => Self::FriendsOnly,
27            3 => Self::Public,
28            _ => Self::Public,
29        }
30    }
31}
32
33/// Profile editing settings.
34#[derive(Debug, Clone, Default, Serialize, Deserialize)]
35pub struct ProfileSettings {
36    /// Display name.
37    pub name: Option<String>,
38    /// Real name.
39    pub real_name: Option<String>,
40    /// Profile summary/bio.
41    pub summary: Option<String>,
42    /// Country code.
43    pub country: Option<String>,
44    /// State/province code.
45    pub state: Option<String>,
46    /// City code.
47    pub city: Option<String>,
48    /// Custom URL (vanity URL).
49    pub custom_url: Option<String>,
50    /// Primary group SteamID.
51    pub primary_group: Option<u64>,
52}
53
54/// Privacy settings for profile elements.
55#[derive(Debug, Clone, Default, Serialize, Deserialize)]
56pub struct PrivacySettings {
57    /// Profile visibility.
58    pub profile: Option<PrivacyState>,
59    /// Comment permissions (uses different values: 0=friends, 1=anyone,
60    /// 2=private).
61    pub comments: Option<i32>,
62    /// Inventory visibility.
63    pub inventory: Option<PrivacyState>,
64    /// Gift inventory visibility.
65    pub inventory_gifts: Option<PrivacyState>,
66    /// Game details visibility.
67    pub game_details: Option<PrivacyState>,
68    /// Playtime visibility.
69    pub playtime: Option<PrivacyState>,
70    /// Friends list visibility.
71    pub friends_list: Option<PrivacyState>,
72}
73
74impl PrivacySettings {
75    /// Convert to the JSON format Steam expects.
76    pub fn to_steam_format(&self) -> serde_json::Value {
77        serde_json::json!({
78            "PrivacyProfile": self.profile.unwrap_or_default() as i32,
79            "PrivacyInventory": self.inventory.unwrap_or_default() as i32,
80            "PrivacyInventoryGifts": self.inventory_gifts.map(|s| s as i32).unwrap_or(3),
81            "PrivacyOwnedGames": self.game_details.unwrap_or_default() as i32,
82            "PrivacyPlaytime": self.playtime.map(|s| s as i32).unwrap_or(3),
83            "PrivacyFriendsList": self.friends_list.unwrap_or_default() as i32,
84        })
85    }
86
87    /// Parse from Steam's internal response format.
88    pub fn from_steam_json(json: &serde_json::Value) -> Option<Self> {
89        let privacy = json.get("PrivacySettings").or_else(|| json.get("Privacy")?.get("PrivacySettings"))?;
90
91        let comments = json.get("eCommentPermission").or_else(|| json.get("Privacy")?.get("eCommentPermission"))?.as_i64().map(|v| v as i32);
92
93        Some(Self {
94            profile: privacy.get("PrivacyProfile").and_then(|v| v.as_i64()).map(|v| PrivacyState::from(v as i32)),
95            comments,
96            inventory: privacy.get("PrivacyInventory").and_then(|v| v.as_i64()).map(|v| PrivacyState::from(v as i32)),
97            inventory_gifts: privacy.get("PrivacyInventoryGifts").and_then(|v| v.as_i64()).map(|v| PrivacyState::from(v as i32)),
98            game_details: privacy.get("PrivacyOwnedGames").and_then(|v| v.as_i64()).map(|v| PrivacyState::from(v as i32)),
99            playtime: privacy.get("PrivacyPlaytime").and_then(|v| v.as_i64()).map(|v| PrivacyState::from(v as i32)),
100            friends_list: privacy.get("PrivacyFriendsList").and_then(|v| v.as_i64()).map(|v| PrivacyState::from(v as i32)),
101        })
102    }
103
104    /// Merge settings from another instance, keeping Some values.
105    pub fn merge(&mut self, other: PrivacySettings) {
106        if other.profile.is_some() {
107            self.profile = other.profile;
108        }
109        if other.comments.is_some() {
110            self.comments = other.comments;
111        }
112        if other.inventory.is_some() {
113            self.inventory = other.inventory;
114        }
115        if other.inventory_gifts.is_some() {
116            self.inventory_gifts = other.inventory_gifts;
117        }
118        if other.game_details.is_some() {
119            self.game_details = other.game_details;
120        }
121        if other.playtime.is_some() {
122            self.playtime = other.playtime;
123        }
124        if other.friends_list.is_some() {
125            self.friends_list = other.friends_list;
126        }
127    }
128}
129
130/// Parsed profile information.
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct SteamProfile {
133    pub name: String,
134    pub real_name: String,
135    pub online_state: String,
136    pub steam_id: SteamID,
137    pub avatar_hash: String,
138    pub avatar_frame: Option<String>,
139    pub custom_url: String,
140    pub location: String,
141    pub summary: Option<String>,
142    pub not_yet_setup: bool,
143    pub profile_private_info: Option<String>,
144    pub lobby_link: Option<String>,
145    pub add_friend_enable: bool,
146    pub is_private: bool,
147    pub url: String,
148    pub nickname: Option<String>,
149    pub level: Option<u32>,
150    pub day_last_ban: Option<i64>,
151    pub game_ban: Option<GameBanData>,
152    pub state_message_game: Option<String>,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize, Default)]
156pub struct GameBanData {
157    #[serde(default)]
158    pub is_vac_ban: BanStatus,
159    #[serde(default)]
160    pub is_game_ban: BanStatus,
161    #[serde(default)]
162    pub is_trade_ban: bool,
163    pub days_since_last_ban: Option<u32>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
167#[non_exhaustive]
168pub enum BanStatus {
169    #[default]
170    None,
171    Single,
172    Multiple,
173}
174
175impl From<i32> for BanStatus {
176    fn from(v: i32) -> Self {
177        match v {
178            1 => BanStatus::Single,
179            _ => BanStatus::None,
180        }
181    }
182}
183
184/// Response from an avatar upload.
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct AvatarUploadResponse {
187    /// The full URL of the uploaded avatar.
188    pub url: String,
189    /// The SHA hash of the uploaded avatar.
190    pub hash: String,
191}
192
193/// Lightweight profile information returned by `resolve_users`.
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct SteamUserProfile {
196    #[serde(rename = "steamId")]
197    pub steam_id: SteamID,
198    #[serde(rename = "accountId")]
199    pub account_id: u32,
200    #[serde(rename = "persona_name")]
201    pub name: String,
202    #[serde(rename = "real_name")]
203    pub real_name: String,
204    #[serde(rename = "avatar_url")]
205    pub avatar_hash: String,
206    #[serde(rename = "profile_url")]
207    pub custom_url: String,
208    #[serde(rename = "persona_state")]
209    pub persona_state: i32,
210    pub city: String,
211    pub state: String,
212    pub country: String,
213    #[serde(rename = "is_friend")]
214    pub is_friend: bool,
215    #[serde(rename = "friends_in_common")]
216    pub friends_in_common: u32,
217}
218
219/// Entry in a user's avatar history.
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct AvatarHistoryEntry {
222    /// The SHA1 hash of the avatar.
223    pub avatar_sha1: String,
224    /// Whether the avatar was uploaded by the user.
225    pub user_uploaded: bool,
226    /// The timestamp when the avatar was used.
227    pub timestamp: u32,
228}
229
230/// User summary from XML profile endpoint (`/profiles/{steamid}/?xml=1`).
231///
232/// This represents the parsed data from Steam's XML profile format.
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct UserSummaryXml {
235    /// Display name (persona name).
236    pub name: String,
237    /// Real name if provided.
238    pub real_name: Option<String>,
239    /// 64-bit Steam ID.
240    pub steam_id: SteamID,
241    /// Online state: "offline", "online", "in-game".
242    pub online_state: String,
243    /// Parsed state message: "offline", "online", "in-game".
244    pub state_message: String,
245    /// Game name if in-game (Steam game).
246    pub state_message_game: Option<String>,
247    /// Game name if in non-Steam game.
248    pub state_message_non_steam_game: Option<String>,
249    /// Privacy state: "public", "private", "friendsonly".
250    pub privacy_state: String,
251    /// Visibility state: 1 (private), 2 (friends only), 3 (public).
252    pub visibility_state: Option<i32>,
253    /// Avatar hash (hash portion of avatar URL).
254    pub avatar_hash: String,
255    /// VAC ban status: 0 = no ban, 1+ = banned.
256    pub vac_banned: Option<i32>,
257    /// Trade ban state: "None" or ban description.
258    pub trade_ban_state: Option<String>,
259    /// Whether this is a limited account.
260    pub is_limited_account: Option<bool>,
261    /// Custom URL (vanity URL path).
262    pub custom_url: Option<String>,
263    /// Member since timestamp (Unix milliseconds).
264    pub member_since: Option<i64>,
265    /// Steam rating.
266    pub steam_rating: Option<String>,
267    /// Location string.
268    pub location: Option<String>,
269    /// Profile summary/bio.
270    pub summary: Option<String>,
271    /// Privacy message if profile is private.
272    pub privacy_message: Option<String>,
273    /// Whether profile has not been set up yet.
274    pub not_yet_setup: bool,
275}
276
277/// User summary from HTML profile page (parsed).
278///
279/// This represents the parsed data from Steam's HTML profile page.
280#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct UserSummaryProfile {
282    /// Display name (persona name).
283    pub name: String,
284    /// Real name if provided.
285    pub real_name: String,
286    /// Online state: "offline", "online", "in-game".
287    pub online_state: String,
288    /// 64-bit Steam ID.
289    pub steam_id: SteamID,
290    /// Avatar hash.
291    pub avatar_hash: String,
292    /// Avatar frame URL if equipped.
293    pub avatar_frame: Option<String>,
294    /// Custom URL (vanity path).
295    pub custom_url: String,
296    /// Country location code.
297    pub location: String,
298    /// Profile summary/bio.
299    pub summary: Option<String>,
300    /// Whether profile has not been set up yet.
301    pub not_yet_setup: bool,
302    /// Private profile info message.
303    pub profile_private_info: Option<String>,
304    /// Steam lobby link if in a joinable lobby.
305    pub lobby_link: Option<String>,
306    /// Whether the "Add Friend" button is visible.
307    pub add_friend_enable: bool,
308    /// Whether the profile is private.
309    pub is_private: bool,
310    /// Full profile URL.
311    pub url: String,
312    /// Nickname set by the viewer for this user.
313    pub nickname: Option<String>,
314    /// Steam level.
315    pub level: Option<u32>,
316    /// Timestamp of last ban (Unix milliseconds).
317    pub day_last_ban: Option<i64>,
318    /// Game ban information.
319    pub game_ban: Option<GameBanData>,
320    /// Current game name if in-game.
321    pub state_message_game: Option<String>,
322}
323
324/// Steam Community online state, as reported by the public profile XML.
325///
326/// `Other` covers any state Valve adds in the future without breaking
327/// downstream code (Snooze, Looking to Trade, etc.).
328#[derive(Debug, Clone, PartialEq, Eq)]
329pub enum OnlineState {
330    Online,
331    Offline,
332    InGame,
333    Snooze,
334    Busy,
335    Away,
336    LookingToTrade,
337    LookingToPlay,
338    Other(String),
339}
340
341impl OnlineState {
342    pub(crate) fn from_xml(s: &str) -> Self {
343        match s {
344            "online" => Self::Online,
345            "offline" => Self::Offline,
346            "in-game" => Self::InGame,
347            "snooze" => Self::Snooze,
348            "busy" => Self::Busy,
349            "away" => Self::Away,
350            "looking to trade" => Self::LookingToTrade,
351            "looking to play" => Self::LookingToPlay,
352            other => Self::Other(other.to_owned()),
353        }
354    }
355}
356
357/// Trade-ban status reported by Steam.
358#[derive(Debug, Clone, PartialEq, Eq)]
359pub enum TradeBanState {
360    None,
361    Probation,
362    Banned,
363    Other(String),
364}
365
366impl TradeBanState {
367    pub(crate) fn from_xml(s: &str) -> Self {
368        match s {
369            "None" => Self::None,
370            "Probation" => Self::Probation,
371            "Banned" => Self::Banned,
372            other => Self::Other(other.to_owned()),
373        }
374    }
375}
376
377/// Snapshot of a Steam profile as returned by the public
378/// `steamcommunity.com/id/{vanity}/?xml=1` (or `/profiles/{id}/?xml=1`) feed.
379///
380/// Anonymous; no API key required. Field availability depends on the
381/// profile's privacy setting — fields wrapped in `Option` are populated only
382/// for profiles where the corresponding section is public.
383#[derive(Debug, Clone)]
384pub struct PublicProfileSummary {
385    /// 64-bit Steam ID.
386    pub steam_id: SteamID,
387    /// Display name (`<steamID>` in XML — confusingly named by Valve).
388    pub persona_name: String,
389    /// Online status (online / offline / in-game / …).
390    pub online_state: OnlineState,
391    /// Free-form status text (e.g. `"Last Online 3 days ago"`).
392    pub state_message: String,
393    /// Profile visibility — collapses Steam's redundant `<privacyState>`
394    /// (string) and `<visibilityState>` (1/2/3) tags into one value.
395    pub privacy_state: PrivacyState,
396    /// Small (32x32) avatar URL.
397    pub avatar_icon: String,
398    /// Medium (64x64) avatar URL.
399    pub avatar_medium: String,
400    /// Full (184x184) avatar URL.
401    pub avatar_full: String,
402    /// VAC banned at least once.
403    pub vac_banned: bool,
404    /// Trade-ban state.
405    pub trade_ban_state: TradeBanState,
406    /// Limited account (no purchase activation, etc.).
407    pub is_limited_account: bool,
408    /// Custom URL slug (only for profiles that set one).
409    pub custom_url: Option<String>,
410    /// Account creation date. Steam serves this localized; the client pins
411    /// `l=english`, so parsing as `"%B %d, %Y"` is stable. Stored as a
412    /// `DateTime<Utc>` at midnight UTC (Steam reports day granularity only).
413    /// `None` if absent or unparseable.
414    pub member_since: Option<DateTime<Utc>>,
415    /// Profile headline.
416    pub headline: Option<String>,
417    /// User-supplied location.
418    pub location: Option<String>,
419    /// Real name.
420    pub real_name: Option<String>,
421    /// Profile summary (HTML).
422    pub summary: Option<String>,
423    /// Hours played in last 2 weeks across all games.
424    pub hours_played_2wk: Option<f32>,
425}