use crate::api_client::get_http_client; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use reqwest::Client; use base64::{Engine as _, engine::general_purpose}; use hmac::{Hmac, Mac}; use sha1::Sha1; use std::time::{SystemTime, UNIX_EPOCH}; #[derive(Debug, Serialize, Deserialize)] pub struct SteamGuardAccount { pub shared_secret: String, pub identity_secret: String, pub account_name: String, pub device_id: String, pub session: SessionData, } #[derive(Debug, Serialize, Deserialize)] pub struct SessionData { pub steam_id: u64, pub access_token: String, pub refresh_token: String, } #[derive(Debug, Serialize, Deserialize)] pub struct Confirmation { pub id: String, pub key: String, pub conf_type: i32, pub creator: u64, pub headline: String, pub summary: Vec, pub accept: String, pub cancel: String, pub icon: Option, } #[derive(Debug, Serialize, Deserialize)] struct ConfirmationsResponse { success: bool, message: Option, need_authentication: bool, confirmations: Vec, } #[derive(Debug, Serialize, Deserialize)] struct SendConfirmationResponse { success: bool, } impl SteamGuardAccount { /// 生成Steam验证码 pub fn generate_code(&self) -> Result> { let time = Self::get_steam_time(); self.generate_code_for_time(time) } /// 为指定时间生成Steam验证码 pub fn generate_code_for_time(&self, time: u64) -> Result> { let shared_secret = general_purpose::STANDARD.decode(&self.shared_secret)?; // 计算时间窗口 let time_bytes = (time / 30).to_be_bytes(); let mut time_buffer = [0u8; 8]; time_buffer.copy_from_slice(&time_bytes[4..8]); // 只取后4字节 // HMAC-SHA1 let mut mac = Hmac::::new_from_slice(&shared_secret)?; mac.update(&time_buffer); let result = mac.finalize().into_bytes(); // 动态截断 let offset = (result[19] & 0x0F) as usize; let mut code_bytes = [0u8; 4]; code_bytes.copy_from_slice(&result[offset..offset + 4]); let code = u32::from_be_bytes(code_bytes); // 生成5位验证码 const STEAM_CHARS: &[u8] = b"23456789BCDFGHJKMNPQRTVWXY"; let mut code_str = String::with_capacity(5); for i in 0..5 { let index = ((code >> (i * 5)) & 0x1F) as usize; code_str.push(STEAM_CHARS[index] as char); } Ok(code_str.chars().rev().collect()) } /// 获取Steam服务器时间 pub fn get_steam_time() -> u64 { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs(); // Steam时间偏移(通常需要从API获取,这里简化处理) now + 2 // 简单的2秒偏移 } /// 生成确认查询参数 fn generate_confirmation_query_params(&self, tag: &str, time: u64) -> HashMap { let identity_secret = general_purpose::STANDARD.decode(&self.identity_secret).unwrap(); // 生成确认哈希 let time_bytes = time.to_be_bytes(); let tag_bytes = tag.as_bytes(); let mut data = Vec::with_capacity(8 + tag_bytes.len()); data.extend_from_slice(&time_bytes); data.extend_from_slice(tag_bytes); let mut mac = Hmac::::new_from_slice(&identity_secret).unwrap(); mac.update(&data); let hash = mac.finalize().into_bytes(); let mut params = HashMap::new(); params.insert("p".to_string(), self.device_id.clone()); params.insert("a".to_string(), self.session.steam_id.to_string()); params.insert("k".to_string(), general_purpose::STANDARD.encode(&hash)); params.insert("t".to_string(), time.to_string()); params.insert("m".to_string(), "react".to_string()); params.insert("tag".to_string(), tag.to_string()); params } /// 获取确认列表 pub async fn fetch_confirmations(&self) -> Result, Box> { let client = get_http_client(); let time = Self::get_steam_time(); let params = self.generate_confirmation_query_params("conf", time); let query_string = params.iter() .map(|(k, v)| format!("{}={}", k, v)) .collect::>() .join("&"); let url = format!("https://steamcommunity.com/mobileconf/getlist?{}", query_string); let response = client .get(&url) .header("User-Agent", "Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36") .header("Cookie", format!("steamLoginSecure={}", self.session.access_token)) .send() .await?; let response_text = response.text().await?; let confirmations_response: ConfirmationsResponse = serde_json::from_str(&response_text)?; if !confirmations_response.success { return Err(format!("Failed to fetch confirmations: {:?}", confirmations_response.message).into()); } Ok(confirmations_response.confirmations) } /// 接受确认 pub async fn accept_confirmation(&self, conf: &Confirmation) -> Result> { self.send_confirmation_ajax(conf, "allow").await } /// 拒绝确认 pub async fn deny_confirmation(&self, conf: &Confirmation) -> Result> { self.send_confirmation_ajax(conf, "cancel").await } /// 发送确认操作 async fn send_confirmation_ajax(&self, conf: &Confirmation, op: &str) -> Result> { let client = get_http_client(); let time = Self::get_steam_time(); let tag = if op == "allow" { "accept" } else { "reject" }; let mut params = self.generate_confirmation_query_params(tag, time); params.insert("op".to_string(), op.to_string()); params.insert("cid".to_string(), conf.id.clone()); params.insert("ck".to_string(), conf.key.clone()); let query_string = params.iter() .map(|(k, v)| format!("{}={}", k, v)) .collect::>() .join("&"); let url = format!("https://steamcommunity.com/mobileconf/ajaxop?{}", query_string); let response = client .get(&url) .header("User-Agent", "Mozilla/5.0 (Linux; Android 10; SM-G973F) AppleWebKit/537.36") .header("Cookie", format!("steamLoginSecure={}", self.session.access_token)) .send() .await?; let response_text = response.text().await?; let conf_response: SendConfirmationResponse = serde_json::from_str(&response_text)?; Ok(conf_response.success) } }