add incomplete account linker
This commit is contained in:
parent
c936d9a0ac
commit
d8ffc179e0
2 changed files with 152 additions and 30 deletions
|
@ -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::<Url>().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
|
||||||
|
}
|
96
src/main.rs
96
src/main.rs
|
@ -1,6 +1,7 @@
|
||||||
extern crate rpassword;
|
extern crate rpassword;
|
||||||
use borrow::BorrowMut;
|
use borrow::BorrowMut;
|
||||||
use io::Write;
|
use io::Write;
|
||||||
|
use steamapi::Session;
|
||||||
use steamguard_cli::*;
|
use steamguard_cli::*;
|
||||||
use ::std::*;
|
use ::std::*;
|
||||||
use text_io::read;
|
use text_io::read;
|
||||||
|
@ -12,6 +13,7 @@ use regex::Regex;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
mod accountmanager;
|
mod accountmanager;
|
||||||
|
mod accountlinker;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CAPTCHA_VALID_CHARS: Regex = Regex::new("^([A-H]|[J-N]|[P-R]|[T-Z]|[2-4]|[7-9]|[@%&])+$").unwrap();
|
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.")
|
.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();
|
.get_matches();
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,6 +94,15 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest.load_accounts();
|
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<SteamGuardAccount> = vec![];
|
let mut selected_accounts: Vec<SteamGuardAccount> = vec![];
|
||||||
if matches.is_present("all") {
|
if matches.is_present("all") {
|
||||||
// manifest.accounts.iter().map(|a| selected_accounts.push(a.b));
|
// manifest.accounts.iter().map(|a| selected_accounts.push(a.b));
|
||||||
|
@ -111,36 +126,9 @@ fn main() {
|
||||||
|
|
||||||
if matches.is_present("trade") {
|
if matches.is_present("trade") {
|
||||||
info!("trade");
|
info!("trade");
|
||||||
for account in selected_accounts.iter_mut() {
|
for a in selected_accounts.iter_mut() {
|
||||||
let _ = std::io::stdout().flush();
|
let mut account = a; // why is this necessary?
|
||||||
let password = rpassword::prompt_password_stdout("Password: ").unwrap();
|
do_login(&mut account);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Checking for trade confirmations");
|
info!("Checking for trade confirmations");
|
||||||
account.get_trade_confirmations();
|
account.get_trade_confirmations();
|
||||||
|
@ -198,3 +186,51 @@ fn prompt_captcha_text(captcha_gid: &String) -> String {
|
||||||
}
|
}
|
||||||
return captcha_text;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue