diff --git a/src/accountlinker.rs b/src/accountlinker.rs index e69de29..12faa1f 100644 --- a/src/accountlinker.rs +++ b/src/accountlinker.rs @@ -0,0 +1,86 @@ +use std::collections::HashMap; +use reqwest::{Url, cookie::{CookieStore}, header::COOKIE, header::{SET_COOKIE, USER_AGENT}}; +use serde::{Serialize, Deserialize}; +use serde_json::Value; +use steamguard_cli::{SteamGuardAccount, steamapi::Session}; +use log::*; + +#[derive(Debug, Clone)] +pub struct AccountLinker { + device_id: String, + phone_number: String, + pub account: SteamGuardAccount, + client: reqwest::blocking::Client, +} + +impl AccountLinker { + pub fn new() -> AccountLinker { + return AccountLinker{ + device_id: generate_device_id(), + phone_number: String::from(""), + account: SteamGuardAccount::new(), + client: reqwest::blocking::ClientBuilder::new() + .cookie_store(true) + .build() + .unwrap(), + } + } + + pub fn link(&self, session: &mut Session) { + let mut params = HashMap::new(); + params.insert("access_token", session.token.clone()); + params.insert("steamid", session.steam_id.to_string()); + params.insert("device_identifier", self.device_id.clone()); + params.insert("authenticator_type", String::from("1")); + params.insert("sms_phone_id", String::from("1")); + } + + fn has_phone(&self, session: &Session) -> bool { + return self._phoneajax(session, "has_phone", "null"); + } + + fn _phoneajax(&self, session: &Session, op: &str, arg: &str) -> bool { + trace!("_phoneajax: op={}", op); + let url = "https://steamcommunity.com".parse::().unwrap(); + let cookies = reqwest::cookie::Jar::default(); + cookies.add_cookie_str("mobileClientVersion=0 (2.1.3)", &url); + cookies.add_cookie_str("mobileClient=android", &url); + cookies.add_cookie_str("Steam_Language=english", &url); + + let mut params = HashMap::new(); + params.insert("op", op); + params.insert("arg", arg); + params.insert("sessionid", session.session_id.as_str()); + if op == "check_sms_code" { + params.insert("checkfortos", "0"); + params.insert("skipvoip", "1"); + } + + let resp = self.client + .post("https://steamcommunity.com/steamguard/phoneajax") + .header(COOKIE, cookies.cookies(&url).unwrap()) + .send() + .unwrap(); + + let result: Value = resp.json().unwrap(); + if result["has_phone"] != Value::Null { + trace!("found has_phone field"); + return result["has_phone"].as_bool().unwrap(); + } else if result["success"] != Value::Null { + trace!("found success field"); + return result["success"].as_bool().unwrap(); + } else { + trace!("did not find any expected field"); + return false; + } + } +} + +fn generate_device_id() -> String { + return format!("android:{}", uuid::Uuid::new_v4().to_string()); +} + +#[derive(Debug, Clone, Deserialize)] +pub struct AddAuthenticatorResponse { + pub response: SteamGuardAccount +} diff --git a/src/main.rs b/src/main.rs index 2f29419..e274943 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ extern crate rpassword; use borrow::BorrowMut; use io::Write; +use steamapi::Session; use steamguard_cli::*; use ::std::*; use text_io::read; @@ -12,6 +13,7 @@ use regex::Regex; #[macro_use] extern crate lazy_static; mod accountmanager; +mod accountlinker; lazy_static! { static ref CAPTCHA_VALID_CHARS: Regex = Regex::new("^([A-H]|[J-N]|[P-R]|[T-Z]|[2-4]|[7-9]|[@%&])+$").unwrap(); @@ -67,6 +69,10 @@ fn main() { .help("Accept all open trade confirmations. Does not open interactive interface.") ) ) + .subcommand( + App::new("setup") + .about("Set up a new account with steamguard-cli") + ) .get_matches(); @@ -88,6 +94,15 @@ fn main() { } manifest.load_accounts(); + + if matches.is_present("setup") { + info!("setup"); + let mut linker = accountlinker::AccountLinker::new(); + do_login(&mut linker.account); + // linker.link(linker.account.session.expect("no login session")); + return; + } + let mut selected_accounts: Vec = vec![]; if matches.is_present("all") { // manifest.accounts.iter().map(|a| selected_accounts.push(a.b)); @@ -111,36 +126,9 @@ fn main() { if matches.is_present("trade") { info!("trade"); - for account in selected_accounts.iter_mut() { - let _ = std::io::stdout().flush(); - let password = rpassword::prompt_password_stdout("Password: ").unwrap(); - trace!("password: {}", password); - let mut login = steamapi::UserLogin::new(account.account_name.clone(), password); - let mut loops = 0; - loop { - match login.login() { - steamapi::LoginResult::Ok(s) => { - account.session = Option::Some(s); - break; - } - steamapi::LoginResult::Need2FA => { - let server_time = steamapi::get_server_time(); - login.twofactor_code = account.generate_code(server_time); - } - steamapi::LoginResult::NeedCaptcha{ captcha_gid } => { - login.captcha_text = prompt_captcha_text(&captcha_gid); - } - r => { - error!("Fatal login result: {:?}", r); - return; - } - } - loops += 1; - if loops > 2 { - error!("Too many loops. Aborting login process, to avoid getting rate limited."); - return; - } - } + for a in selected_accounts.iter_mut() { + let mut account = a; // why is this necessary? + do_login(&mut account); info!("Checking for trade confirmations"); account.get_trade_confirmations(); @@ -198,3 +186,51 @@ fn prompt_captcha_text(captcha_gid: &String) -> String { } return captcha_text; } + +fn do_login(account: &mut SteamGuardAccount) { + if account.account_name.len() > 0 { + println!("Username: {}", account.account_name); + } else { + print!("Username: "); + account.account_name = prompt(); + } + let _ = std::io::stdout().flush(); + let password = rpassword::prompt_password_stdout("Password: ").unwrap(); + if password.len() > 0 { + debug!("password is present"); + } else { + debug!("password is empty"); + } + // TODO: reprompt if password is empty + let mut login = steamapi::UserLogin::new(account.account_name.clone(), password); + let mut loops = 0; + loop { + match login.login() { + steamapi::LoginResult::Ok(s) => { + account.session = Option::Some(s); + break; + } + steamapi::LoginResult::Need2FA => { + let server_time = steamapi::get_server_time(); + login.twofactor_code = account.generate_code(server_time); + } + steamapi::LoginResult::NeedCaptcha{ captcha_gid } => { + login.captcha_text = prompt_captcha_text(&captcha_gid); + } + steamapi::LoginResult::NeedEmail => { + println!("You should have received an email with a code."); + print!("Enter code"); + login.email_code = prompt(); + } + r => { + error!("Fatal login result: {:?}", r); + return; + } + } + loops += 1; + if loops > 2 { + error!("Too many loops. Aborting login process, to avoid getting rate limited."); + return; + } + } +}