1use regex::Regex;
2
3use crate::Error;
4
5#[derive(Debug, Serialize, Deserialize)]
6struct LoginData {
7 #[serde(rename = "openid.ns")]
8 ns: String,
9 #[serde(rename = "openid.mode")]
10 mode: String,
11 #[serde(rename = "openid.op_endpoint")]
12 op_endpoint: String,
13 #[serde(rename = "openid.claimed_id")]
14 claimed_id: String,
15 #[serde(rename = "openid.identity")]
16 identity: String,
17 #[serde(rename = "openid.return_to")]
18 return_to: String,
19 #[serde(rename = "openid.response_nonce")]
20 response_nonce: String,
21 #[serde(rename = "openid.invalidate_handle")]
22 invalidate_handle: Option<String>,
23 #[serde(rename = "openid.assoc_handle")]
24 assoc_handle: String,
25 #[serde(rename = "openid.signed")]
26 signed: String,
27 #[serde(rename = "openid.sig")]
28 sig: String,
29}
30
31impl LoginData {
32 pub fn claim_id(&self) -> Result<u64, Error> {
33 lazy_static! {
34 static ref RE_STEAM_ID64: Regex =
35 Regex::new("^(http|https)://steamcommunity.com/openid/id/([0-9]{17}$)").unwrap();
36 }
37
38 RE_STEAM_ID64
39 .captures(self.claimed_id.as_str())
40 .ok_or(Error::ParseSteamID("Invalid claimed url".to_owned()))?
41 .get(2)
42 .ok_or(Error::ParseSteamID(
43 "Failed to retrieve SteamID64".to_owned(),
44 ))?
45 .as_str()
46 .parse::<u64>()
47 .map_err(|e| Error::ParseSteamID(e.to_string()))
48 }
49}
50
51#[allow(dead_code)]
52#[derive(Debug, Clone, Deserialize)]
53pub struct PlayerSummaries {
54 pub steamid: String,
55 pub personaname: String,
56 pub profileurl: String,
57 pub avatar: String,
58 pub avatarmedium: String,
59 pub avatarfull: String,
60 pub avatarhash: Option<String>,
61 pub personastate: Option<i32>,
62 pub personastateflags: Option<i32>,
63 pub communityvisibilitystate: u32,
64 pub profilestate: Option<i32>,
65 pub lastlogoff: Option<i64>,
66 pub commentpermission: Option<u32>,
67 pub realname: Option<String>,
68 pub primaryclanid: Option<String>,
69 pub timecreated: Option<u64>,
70 pub gameid: Option<String>,
71 pub gameserverip: Option<String>,
72 pub gameextrainfo: Option<String>,
73 pub cityid: Option<i64>,
74 pub loccountrycode: Option<String>,
75 pub locstatecode: Option<String>,
76 pub loccityid: Option<i64>,
77}
78
79#[derive(Deserialize)]
80struct SummariesPlayers {
81 players: Vec<PlayerSummaries>,
82}
83
84#[derive(Deserialize)]
85struct SummariesResponse {
86 response: SummariesPlayers,
87}
88
89#[derive(Debug)]
90pub struct Verify {
91 claimed_id: u64,
92}
93
94impl Verify {
95 async fn is_valid(&self, data: &LoginData) -> Result<bool, Error> {
96 let form = serde_qs::to_string(&data).map_err(|e| Error::ParseSteamID(e.to_string()))?;
97
98 let client = reqwest::Client::new();
99 let response = client
100 .post("https://steamcommunity.com/openid/login")
101 .header("Content-Type", "application/x-www-form-urlencoded")
102 .body(form)
103 .send()
104 .await
105 .map_err(|e| Error::ParseSteamID(e.to_string()))?
106 .text()
107 .await
108 .map_err(|e| Error::ParseSteamID(e.to_string()))?;
109
110 let is_valid = response
111 .split("\n")
112 .filter_map(|line| {
113 let mut pair = line.splitn(2, ":");
114 Some((pair.next()?, pair.next()?))
115 })
116 .any(|(k, v)| k == "is_valid" && v == "true");
117
118 Ok(is_valid)
119 }
120
121 pub async fn verify_request(query_string: &str) -> Result<Self, Error> {
123 let mut data = serde_qs::from_str::<LoginData>(query_string).map_err(Error::Deserialize)?;
124 data.mode = "check_authentication".to_owned();
125
126 let verify = Self {
127 claimed_id: data.claim_id()?,
128 };
129
130 if !verify.is_valid(&data).await? {
131 return Err(Error::ParseSteamID("Invalid data".to_string()));
132 }
133
134 Ok(verify)
135 }
136
137 pub async fn get_summaries(&self, apikey: &str) -> Result<PlayerSummaries, Error> {
139 let steamid = self.claimed_id.to_string();
140
141 let client = reqwest::Client::new();
142 let response = client
143 .get("https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/")
144 .query(&[("key", apikey), ("steamids", steamid.as_str())])
145 .send()
146 .await
147 .map_err(|e| Error::GetSummaries(e.to_string()))?
148 .json::<SummariesResponse>()
149 .await
150 .map_err(|e| Error::GetSummaries(e.to_string()))?;
151
152 let player = response
153 .response
154 .players
155 .first()
156 .ok_or(Error::GetSummaries("Failed to find player".to_owned()))?;
157
158 Ok(player.clone())
159 }
160
161 pub fn claim_id(&self) -> u64 {
163 self.claimed_id
164 }
165}