Skip to main content

steamid_utils/
lib.rs

1use std::num::ParseIntError;
2
3#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4pub enum SteamIdKind {
5    Steam64,
6    Steam32,
7    Steam3,
8}
9
10pub fn parse_incoming_format(steam_id: &str) -> Result<SteamIdKind, SteamIdError> {
11    if steam_id.len() == 17 && steam_id.starts_with("7656119") {
12        return Ok(SteamIdKind::Steam64);
13    }
14
15    if steam_id.to_ascii_uppercase().starts_with("STEAM_") {
16        return Ok(SteamIdKind::Steam32);
17    }
18
19    if steam_id.starts_with("[U:") && steam_id.ends_with(']') {
20        return Ok(SteamIdKind::Steam3);
21    }
22
23    return Err(SteamIdError::InvalidFormat);
24}
25
26pub fn to_steam32(steam_id: &str) -> Result<String, SteamIdError> {
27    let incoming_format = parse_incoming_format(steam_id)?; // ← unwrap here with ?
28
29    match incoming_format {
30        SteamIdKind::Steam64 => {
31            let steamid64: u64 = steam_id.parse()?;
32
33            const UNIVERSE_BASE: u64 = 76561197960265728;
34            if steamid64 < UNIVERSE_BASE {
35                return Err(SteamIdError::InvalidFormat);
36            }
37
38            let account_id = steamid64 - UNIVERSE_BASE;
39            let middle_digit = account_id % 2;
40            let auth_server = account_id / 2;
41
42            Ok(format!("STEAM_0:{}:{}", middle_digit, auth_server))
43        }
44
45        SteamIdKind::Steam32 => Ok(steam_id.to_string()),
46
47        SteamIdKind::Steam3 => {
48            let input = steam_id.trim();
49
50            let cleaned = if input.starts_with('[') && input.ends_with(']') {
51                &input[1..input.len() - 1]
52            } else {
53                input
54            };
55
56            let parts: Vec<&str> = cleaned.split(':').collect();
57            if parts.len() != 3 {
58                return Err(SteamIdError::InvalidFormat);
59            }
60
61            if parts[0] != "U" {
62                return Err(SteamIdError::InvalidPrefix);
63            }
64
65            if parts[1] != "1" {
66                return Err(SteamIdError::InvalidFormat);
67            }
68
69            let account_id_str = parts[2];
70            let account_id: u32 = account_id_str.parse().map_err(SteamIdError::ParseError)?;
71            let y = account_id % 2;
72            let z = account_id / 2;
73
74            Ok(format!("STEAM_0:{}:{}", y, z))
75        }
76    }
77}
78
79pub fn to_steam64(steam_id: &str) -> Result<String, SteamIdError> {
80    let incoming_format = parse_incoming_format(steam_id)?;
81    match incoming_format {
82        SteamIdKind::Steam64 => Ok(steam_id.to_string()),
83        SteamIdKind::Steam32 => {
84            const STEAMID64_BASE: u64 = 76561197960265728;
85            let parts: Vec<&str> = steam_id.split(':').collect();
86            let y: u32 = parts[1].parse().map_err(|_| SteamIdError::InvalidY)?;
87
88            let z: u64 = parts[2].parse().map_err(|_| SteamIdError::InvalidZ)?;
89
90            if y > 1 {
91                return Err(SteamIdError::InvalidY);
92            }
93            let account_id = z * 2 + u64::from(y);
94
95            let steam64 = STEAMID64_BASE + account_id;
96
97            Ok(steam64.to_string())
98        }
99        SteamIdKind::Steam3 => {
100            let input = steam_id.trim();
101            let cleaned = if input.starts_with('[') && input.ends_with(']') {
102                &input[1..input.len() - 1]
103            } else {
104                input
105            };
106
107            let parts: Vec<&str> = cleaned.split(':').collect();
108            if parts.len() != 3 {
109                return Err(SteamIdError::InvalidFormat);
110            }
111
112            let account_id: u64 = parts[2].parse().map_err(SteamIdError::ParseError)?;
113
114            if account_id == 0 {
115                return Err(SteamIdError::InvalidFormat);
116            }
117
118            const STEAMID64_BASE: u64 = 76561197960265728;
119            let steam64 = STEAMID64_BASE + account_id;
120
121            Ok(steam64.to_string())
122        }
123    }
124}
125
126pub fn to_steam3(steam_id: &str) -> Result<String, SteamIdError> {
127    let incoming_format = parse_incoming_format(steam_id)?;
128    match incoming_format {
129        SteamIdKind::Steam64 => {
130            const STEAMID64_BASE: u64 = 76561197960265728;
131            let id64: u64 = steam_id.parse().map_err(SteamIdError::ParseError)?;
132            if id64 < STEAMID64_BASE {
133                return Err(SteamIdError::InvalidFormat);
134            }
135            let account_id = id64 - STEAMID64_BASE;
136            Ok(format!("[U:1:{}]", account_id))
137        }
138        SteamIdKind::Steam32 => {
139            let parts: Vec<&str> = steam_id.split(':').collect();
140            if parts.len() != 3 {
141                return Err(SteamIdError::InvalidFormat);
142            }
143            let y: u32 = parts[1].parse().map_err(SteamIdError::ParseError)?;
144            let z: u32 = parts[2].parse().map_err(SteamIdError::ParseError)?;
145
146            if y > 1 {
147                return Err(SteamIdError::InvalidFormat);
148            }
149            let account_id = z * 2 + y;
150            Ok(format!("[U:1:{}]", account_id))
151        }
152        SteamIdKind::Steam3 => Ok(steam_id.to_string()),
153    }
154}
155
156#[derive(Debug, Clone, PartialEq, Eq)]
157pub enum SteamIdError {
158    InvalidFormat,
159    InvalidPrefix,
160    InvalidY,
161    InvalidZ,
162    ParseError(ParseIntError),
163}
164
165impl std::fmt::Display for SteamIdError {
166    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167        match self {
168            Self::InvalidFormat => {
169                write!(f, "SteamID32 must have exactly 3 parts separated by ':'")
170            }
171            Self::InvalidPrefix => write!(f, "SteamID32 must start with 'STEAM_0' or 'STEAM_1'"),
172            Self::InvalidY => write!(f, "The second part (Y) must be 0 or 1"),
173            Self::InvalidZ => write!(f, "The third part (Z) must be a valid non-negative integer"),
174            Self::ParseError(e) => write!(f, "Number parsing failed: {}", e),
175        }
176    }
177}
178impl std::error::Error for SteamIdError {}
179
180impl From<ParseIntError> for SteamIdError {
181    fn from(err: ParseIntError) -> Self {
182        SteamIdError::ParseError(err)
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    #[test]
190
191    fn test_to_steam32() {
192        assert_eq!(
193            to_steam32("STEAM_0:1:11110394"),
194            Ok("STEAM_0:1:11110394".to_string())
195        );
196        assert_eq!(
197            to_steam32("76561197982486517"),
198            Ok("STEAM_0:1:11110394".to_string())
199        );
200        assert_eq!(
201            to_steam32("[U:1:22220789]"),
202            Ok("STEAM_0:1:11110394".to_string())
203        );
204    }
205    #[test]
206    fn test_to_steam64() {
207        assert_eq!(
208            to_steam64("STEAM_0:1:11110394"),
209            Ok("76561197982486517".to_string())
210        );
211        assert_eq!(
212            to_steam64("76561197982486517"),
213            Ok("76561197982486517".to_string())
214        );
215        assert_eq!(
216            to_steam64("[U:1:22220789]"),
217            Ok("76561197982486517".to_string())
218        );
219    }
220
221    #[test]
222    fn test_to_steam3() {
223        assert_eq!(
224            to_steam3("[U:1:22220789]"),
225            Ok("[U:1:22220789]".to_string())
226        );
227        assert_eq!(
228            to_steam3("76561197982486517"),
229            Ok("[U:1:22220789]".to_string())
230        );
231        assert_eq!(
232            to_steam3("STEAM_0:1:11110394"),
233            Ok("[U:1:22220789]".to_string())
234        );
235    }
236    #[test]
237    fn test_parse_incoming_format() {
238        assert_eq!(
239            parse_incoming_format("76561197982486517"),
240            Ok(SteamIdKind::Steam64)
241        );
242        assert_eq!(
243            parse_incoming_format("[U:1:22220789]"),
244            Ok(SteamIdKind::Steam3)
245        );
246        assert_eq!(
247            parse_incoming_format("STEAM_0:1:11110394"),
248            Ok(SteamIdKind::Steam32)
249        );
250
251        assert_eq!(
252            parse_incoming_format("7656119798248"),
253            Err(SteamIdError::InvalidFormat)
254        );
255        assert_eq!(
256            parse_incoming_format("[STEAM_0:1:11110394]"),
257            Err(SteamIdError::InvalidFormat)
258        );
259        assert_eq!(
260            parse_incoming_format("U:1:22220789"),
261            Err(SteamIdError::InvalidFormat)
262        );
263    }
264}