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
13lazy_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)]
46pub struct SteamID {
48 account_id: bool,
50 account_number: BitVec<Msb0, u64>,
52 account_instance: BitVec<Msb0, u64>,
53 account_type: BitVec<Msb0, u64>,
55 universe: BitVec<Msb0, u64>,
57}
58
59impl SteamID {
61 pub fn to_steam3(&self) -> u64 {
64 let z = self.account_number.load::<u64>();
67 let y = self.account_id as u64;
68 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 vec[1..].load::<u64>()
84 }
85
86 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 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 pub fn parse(steamid: &str) -> Option<Self> {
130 if REGEX_STEAM3.is_match(steamid) {
131 let captures = REGEX_STEAM3.captures(steamid).unwrap();
132
133 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 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 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}