| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- 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<String>,
- pub accept: String,
- pub cancel: String,
- pub icon: Option<String>,
- }
- #[derive(Debug, Serialize, Deserialize)]
- struct ConfirmationsResponse {
- success: bool,
- message: Option<String>,
- need_authentication: bool,
- confirmations: Vec<Confirmation>,
- }
- #[derive(Debug, Serialize, Deserialize)]
- struct SendConfirmationResponse {
- success: bool,
- }
- impl SteamGuardAccount {
- /// 生成Steam验证码
- pub fn generate_code(&self) -> Result<String, Box<dyn std::error::Error>> {
- let time = Self::get_steam_time();
- self.generate_code_for_time(time)
- }
- /// 为指定时间生成Steam验证码
- pub fn generate_code_for_time(&self, time: u64) -> Result<String, Box<dyn std::error::Error>> {
- 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::<Sha1>::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<String, String> {
- 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::<Sha1>::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<Vec<Confirmation>, Box<dyn std::error::Error>> {
- 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::<Vec<_>>()
- .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<bool, Box<dyn std::error::Error>> {
- self.send_confirmation_ajax(conf, "allow").await
- }
- /// 拒绝确认
- pub async fn deny_confirmation(&self, conf: &Confirmation) -> Result<bool, Box<dyn std::error::Error>> {
- self.send_confirmation_ajax(conf, "cancel").await
- }
- /// 发送确认操作
- async fn send_confirmation_ajax(&self, conf: &Confirmation, op: &str) -> Result<bool, Box<dyn std::error::Error>> {
- 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::<Vec<_>>()
- .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)
- }
- }
|