steamid_parser/
lib.rs

1use std::str::FromStr;
2
3use bitvec::prelude::*;
4use lazy_static::lazy_static;
5use num::FromPrimitive;
6use regex::Regex;
7
8#[cfg(feature = "serialize")]
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10
11use steam_language_gen::generated::enums::{EAccountType, EUniverse};
12
13// TODO - Error catching
14
15lazy_static! {
16    static ref REGEX_STEAM2: Regex =
17        Regex::new(r"STEAM_(?P<universe>[0-4]):(?P<authserver>[0-1]):(?P<accountid>\d+)").unwrap();
18    static ref REGEX_STEAM3: Regex =
19        Regex::new(r"\[(?P<type>[AGMPCgcLTIUai]):(?P<universe>[0-4]):(?P<account>\d+)\]").unwrap();
20    static ref REGEX_STEAM64: Regex = Regex::new(r"(?P<account>7\d{16})").unwrap();
21    static ref REGEX_STEAM3_FALLBACK: Regex = Regex::new(r"").unwrap();
22}
23
24struct AccountType(EAccountType);
25
26impl AccountType {
27    fn new(identifier: &str) -> Option<Self> {
28        let kind = match identifier {
29            "A" => EAccountType::AnonGameServer,
30            "G" => EAccountType::GameServer,
31            "M" => EAccountType::Multiseat,
32            "P" => EAccountType::Pending,
33            "C" => EAccountType::ContentServer,
34            "g" => EAccountType::Clan,
35            "T" => EAccountType::Chat,
36            "I" => EAccountType::Invalid,
37            "U" => EAccountType::Individual,
38            "a" => EAccountType::AnonUser,
39            _ => return None,
40        };
41        Some(Self { 0: kind })
42    }
43}
44
45#[derive(Debug, Clone, PartialEq)]
46/// Let X, Y and Z constants be defined by the SteamID: STEAM_X:Y:Z.
47pub struct SteamID {
48    /// ID number of account. Either 0 or 1
49    account_id: bool,
50    /// Account Number. Z
51    account_number: BitVec<Msb0, u64>,
52    account_instance: BitVec<Msb0, u64>,
53    /// 4 Bits.
54    account_type: BitVec<Msb0, u64>,
55    /// Universe. 8 Bits
56    universe: BitVec<Msb0, u64>,
57}
58
59/// Reference: https://developer.valvesoftware.com/wiki/SteamID
60impl SteamID {
61    /// Using the formula W=Z*2+Y, a SteamID can be converted to Steam3.
62    /// Source: https://steamcommunity.com/path/[letter:1:W]
63    pub fn to_steam3(&self) -> u64 {
64        // let steamid64_identifier: u64 = 0x0110_0001_0000_0000;
65
66        let z = self.account_number.load::<u64>();
67        let y = self.account_id as u64;
68        // let x = self.universe.load::<u64>();
69
70        z * 2 + y
71    }
72
73    pub fn to_steam64(&self) -> u64 {
74        let mut vec: BitVec<Msb0> = BitVec::with_capacity(64);
75        vec.extend_from_slice(self.universe.as_bitslice());
76        vec.extend_from_slice(self.account_type.as_bitslice());
77        vec.extend_from_slice(self.account_instance.as_bitslice());
78        vec.extend_from_slice(self.account_number.as_bitslice());
79        vec.push(self.account_id);
80
81        // this should be ..64, we are omitting a initial zero(first bit)
82        // from the steamID
83        vec[1..].load::<u64>()
84    }
85
86    /// Creates a new SteamID from the Steam3 format.
87    /// Defaults to Public universe, and Individual account.
88    /// You can use the parse utility function.
89    pub fn from_steam3(steam3: u32, universe: Option<EUniverse>, account_type: Option<EAccountType>) -> Self {
90        let parity_check = steam3 & 1;
91        let universe = universe.unwrap_or(EUniverse::Public) as u64;
92        let account_number = ((steam3 - parity_check) / 2) as u64;
93        let account_type = account_type.unwrap_or(EAccountType::Individual) as u64;
94        let instance = 1u64;
95
96        Self {
97            account_id: parity_check != 0,
98            account_number: BitVec::from(&account_number.bits()[33..]),
99            account_instance: BitVec::from(&instance.bits()[44..]),
100            account_type: BitVec::from(&account_type.bits()[60..]),
101            universe: BitVec::from(&universe.bits()[56..]),
102        }
103    }
104
105    /// Creates a new SteamID from the Steam64 format.
106    pub fn from_steam64(steam64: u64) -> Self {
107        let steam_as_bits = steam64.bits::<Msb0>();
108        let steamid_len = steam_as_bits.len() - 1;
109
110        let account_id = steam_as_bits[steamid_len];
111        let account_number = steam_as_bits[32..steamid_len].to_vec();
112        let account_instance = steam_as_bits[12..32].to_vec();
113        let account_type = steam_as_bits[8..12].to_vec();
114        let universe = steam_as_bits[0..8].to_vec();
115
116        Self {
117            account_id,
118            account_number,
119            account_instance,
120            account_type,
121            universe,
122        }
123    }
124
125    /// Parses the following formats:
126    /// Steam64: digit 7 + 16 digits
127    ///
128    /// Steam3: [T:U:D] where T: The account type, U: The account universe, D: Account number,
129    pub fn parse(steamid: &str) -> Option<Self> {
130        if REGEX_STEAM3.is_match(steamid) {
131            let captures = REGEX_STEAM3.captures(steamid).unwrap();
132
133            // since it got matched, we can unwrap
134            let account_number = captures.name("account").unwrap().as_str();
135            let account_universe = captures.name("universe").unwrap().as_str();
136            let account_type = captures.name("type").unwrap().as_str();
137
138            // TODO - match instance
139            // let account_instance = captures.name("instance");
140
141            return Some(Self::from_steam3(
142                account_number.parse().unwrap(),
143                Some(EUniverse::from_u32(u32::from_str(account_universe).unwrap()).unwrap()),
144                Some(AccountType::new(account_type).unwrap().0),
145            ));
146        } else if REGEX_STEAM64.is_match(steamid) {
147            let captures = REGEX_STEAM64.captures(steamid).unwrap();
148            let number = captures.name("account").unwrap();
149
150            return Some(Self::from_steam64(u64::from_str(number.as_str()).unwrap()));
151        }
152        None
153    }
154}
155
156#[cfg(feature = "serialize")]
157impl Serialize for SteamID {
158    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
159    where
160        S: Serializer,
161    {
162        serializer.serialize_u64(self.to_steam64())
163    }
164}
165
166#[cfg(feature = "serialize")]
167impl<'de> Deserialize<'de> for SteamID {
168    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
169    where
170        D: Deserializer<'de>,
171    {
172        let steamid = u64::deserialize(deserializer)?;
173        Ok(SteamID::from_steam64(steamid))
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    // We are using this for our tests:
182    // https://steamidfinder.com/lookup/76561198092541763/
183    fn get_steam64_odd() -> u64 {
184        76_561_198_092_541_763
185    }
186
187    fn get_steam3() -> u64 {
188        132_276_035
189    }
190
191    fn get_steam3_unformatted() -> &'static str {
192        "[U:1:132276035]"
193    }
194
195    fn get_steam64_even() -> u64 {
196        76561197984835396
197    }
198
199    fn get_steam3_even() -> u64 {
200        24569668
201    }
202
203    #[test]
204    fn steamid_from_steam64() {
205        let steamid = SteamID::from_steam64(get_steam64_odd());
206        assert_eq!(steamid.to_steam64(), get_steam64_odd())
207    }
208
209    #[test]
210    fn steamid_to_steam64() {
211        let steamid = SteamID::from_steam64(get_steam64_odd());
212        assert_eq!(steamid.to_steam64(), get_steam64_odd())
213    }
214
215    #[test]
216    fn steamid_from_steam3_mine() {
217        let steamid = SteamID::from_steam3(get_steam3_even() as u32, None, None);
218        assert_eq!(steamid.to_steam64(), get_steam64_even())
219    }
220
221    #[test]
222    fn steamid64_to_steam3_mine() {
223        let steamid = SteamID::from_steam64(get_steam64_even());
224        assert_eq!(steamid.to_steam3(), get_steam3_even())
225    }
226
227    #[test]
228    fn steamid_to_steam3() {
229        let steamid = SteamID::from_steam64(get_steam64_odd());
230        let steam32 = steamid.to_steam3();
231        assert_eq!(steam32, get_steam3())
232    }
233
234    #[test]
235    fn steamid_from_steam3() {
236        let steamid = SteamID::from_steam3(get_steam3() as u32, None, None);
237        assert_eq!(steamid.to_steam64(), get_steam64_odd())
238    }
239
240    #[test]
241    fn steam64_parse() {
242        let formatted_steamid = format!("text {} xxaasssddff", get_steam64_odd());
243        let steamid = SteamID::parse(&formatted_steamid).unwrap();
244        assert_eq!(steamid.to_steam64(), get_steam64_odd());
245    }
246
247    #[test]
248    fn steam3_parse() {
249        let formatted_steamid = format!("text {} xxaasssddff", get_steam3_unformatted());
250        let steamid = SteamID::parse(&formatted_steamid).unwrap();
251        assert_eq!(steamid.to_steam64(), get_steam64_odd());
252    }
253
254    #[cfg(feature = "serialize")]
255    #[test]
256    fn serde_se_de() {
257        let steamid = SteamID::from_steam64(get_steam64_odd());
258
259        let serialized = serde_json::to_string(&steamid).unwrap();
260        let unserialized: SteamID = serde_json::from_str(&serialized).unwrap();
261
262        assert_eq!(steamid, unserialized);
263        assert_eq!(steamid.to_steam3(), unserialized.to_steam3());
264    }
265}