use std::time::Duration;
use reqwest::Client;
use scraper::{Html, Selector};
use serde::{Deserialize, Serialize};
const MAIL_CONNECT_TIMEOUT: Duration = Duration::from_secs(30);
const MAIL_TIMEOUT: Duration = Duration::from_secs(60);
#[derive(Debug, Serialize, Deserialize)]
struct MailResponse {
mails: Option<Vec<Mail>>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Mail {
body: String,
}
#[derive(Debug, Serialize)]
struct MailFilter {
date: Option<String>,
body: Option<Vec<String>>,
from: Option<String>,
}
fn parse_steam_guard_code(html_content: &str) -> Option<String> {
let document = Html::parse_document(html_content);
let selector = Selector::parse("table tbody tr td").ok()?;
for element in document.select(&selector) {
let style = element.value().attr("style").unwrap_or("");
let text = element.text().collect::<String>().trim().to_string();
let style_lower = style.to_lowercase();
if style_lower.contains("color: #3a9aed") && style_lower.contains("font-weight: bold") && style_lower.contains("text-align: center") && text.len() == 5 && text.to_uppercase() == text {
return Some(text);
}
}
None
}
pub async fn get_mail_steam_guard_codes(macro_url: &str) -> anyhow::Result<Vec<String>> {
if macro_url.is_empty() {
return Ok(vec![]);
}
let filter = MailFilter { date: Some("LAST_10_MIN".to_string()), body: None, from: None };
let filter_json = serde_json::to_string(&filter)?;
let filter_encoded = urlencoding::encode(&filter_json);
let url = format!("{}?action=getEmailInbox&filter={}", macro_url, filter_encoded);
let client = Client::builder().connect_timeout(MAIL_CONNECT_TIMEOUT).timeout(MAIL_TIMEOUT).build()?;
let resp = client.get(&url).send().await?;
if !resp.status().is_success() {
return Ok(vec![]);
}
let result: MailResponse = resp.json().await?;
let mut codes = Vec::new();
if let Some(mails) = result.mails {
for mail in mails {
if let Some(code) = parse_steam_guard_code(&mail.body) {
codes.push(code);
}
}
}
Ok(codes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_steam_guard_code() {
let html_success = r#"
<html>
<body>
<table>
<tbody>
<tr>
<td style="color: #3a9aed; font-weight: bold; text-align: center;">PHV79</td>
</tr>
</tbody>
</table>
</body>
</html>
"#;
assert_eq!(parse_steam_guard_code(html_success), Some("PHV79".to_string()));
let html_fail_color = r#"
<html>
<body>
<table>
<tbody>
<tr>
<td style="color: red; font-weight: bold; text-align: center;">PHV79</td>
</tr>
</tbody>
</table>
</body>
</html>
"#;
assert_eq!(parse_steam_guard_code(html_fail_color), None);
let html_fail_text = r#"
<html>
<body>
<table>
<tbody>
<tr>
<td style="color: #3a9aed; font-weight: bold; text-align: center;">invalid</td>
</tr>
</tbody>
</table>
</body>
</html>
"#;
assert_eq!(parse_steam_guard_code(html_fail_text), None);
}
}