2023-06-23 13:36:23 -04:00
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
|
|
|
|
use crossterm::tty::IsTty;
|
|
|
|
use log::*;
|
2023-06-27 10:20:27 -04:00
|
|
|
use steamguard::{Confirmation, Confirmer, ConfirmerError};
|
2023-06-23 13:36:23 -04:00
|
|
|
|
|
|
|
use crate::{tui, AccountManager};
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Parser)]
|
|
|
|
#[clap(about = "Interactive interface for trade confirmations")]
|
|
|
|
pub struct TradeCommand {
|
|
|
|
#[clap(
|
|
|
|
short,
|
|
|
|
long,
|
|
|
|
help = "Accept all open trade confirmations. Does not open interactive interface."
|
|
|
|
)]
|
|
|
|
pub accept_all: bool,
|
|
|
|
#[clap(
|
|
|
|
short,
|
|
|
|
long,
|
|
|
|
help = "If submitting a confirmation response fails, exit immediately."
|
|
|
|
)]
|
|
|
|
pub fail_fast: bool,
|
|
|
|
}
|
|
|
|
|
2023-07-02 08:57:13 -04:00
|
|
|
impl<T> AccountCommand<T> for TradeCommand
|
|
|
|
where
|
|
|
|
T: Transport + Clone,
|
|
|
|
{
|
2023-06-23 13:36:23 -04:00
|
|
|
fn execute(
|
|
|
|
&self,
|
2023-07-02 08:57:13 -04:00
|
|
|
transport: T,
|
2023-06-23 13:36:23 -04:00
|
|
|
manager: &mut AccountManager,
|
|
|
|
accounts: Vec<Arc<Mutex<SteamGuardAccount>>>,
|
2023-09-28 17:52:42 -04:00
|
|
|
args: &GlobalArgs,
|
2023-06-23 13:36:23 -04:00
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
for a in accounts {
|
|
|
|
let mut account = a.lock().unwrap();
|
|
|
|
|
|
|
|
if !account.is_logged_in() {
|
|
|
|
info!("Account does not have tokens, logging in");
|
2023-09-28 17:52:42 -04:00
|
|
|
crate::do_login(transport.clone(), &mut account, args.password.clone())?;
|
2023-06-23 13:36:23 -04:00
|
|
|
}
|
|
|
|
|
2023-06-24 13:02:54 -04:00
|
|
|
info!("{}: Checking for trade confirmations", account.account_name);
|
2023-06-23 13:36:23 -04:00
|
|
|
let confirmations: Vec<Confirmation>;
|
|
|
|
loop {
|
2023-07-02 08:57:13 -04:00
|
|
|
let confirmer = Confirmer::new(transport.clone(), &account);
|
2023-06-27 10:20:27 -04:00
|
|
|
|
|
|
|
match confirmer.get_trade_confirmations() {
|
2023-06-23 13:36:23 -04:00
|
|
|
Ok(confs) => {
|
|
|
|
confirmations = confs;
|
|
|
|
break;
|
|
|
|
}
|
2023-06-27 10:20:27 -04:00
|
|
|
Err(ConfirmerError::InvalidTokens) => {
|
|
|
|
info!("obtaining new tokens");
|
2023-09-28 17:52:42 -04:00
|
|
|
crate::do_login(transport.clone(), &mut account, args.password.clone())?;
|
2023-06-23 13:36:23 -04:00
|
|
|
}
|
2023-06-27 10:20:27 -04:00
|
|
|
Err(err) => {
|
|
|
|
error!("Failed to get trade confirmations: {}", err);
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
2023-06-23 13:36:23 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-24 13:02:54 -04:00
|
|
|
if confirmations.is_empty() {
|
|
|
|
info!("{}: No confirmations", account.account_name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-07-02 08:57:13 -04:00
|
|
|
let confirmer = Confirmer::new(transport.clone(), &account);
|
2023-06-23 13:36:23 -04:00
|
|
|
let mut any_failed = false;
|
2023-07-24 10:29:49 -04:00
|
|
|
|
|
|
|
fn submit_loop(
|
|
|
|
f: impl Fn() -> Result<(), ConfirmerError>,
|
|
|
|
fail_fast: bool,
|
|
|
|
) -> Result<(), ConfirmerError> {
|
|
|
|
let mut attempts = 0;
|
|
|
|
loop {
|
|
|
|
match f() {
|
|
|
|
Ok(_) => break,
|
|
|
|
Err(ConfirmerError::InvalidTokens) => {
|
|
|
|
error!("Invalid tokens, but they should be valid already. This is weird, stopping.");
|
|
|
|
return Err(ConfirmerError::InvalidTokens);
|
|
|
|
}
|
|
|
|
Err(ConfirmerError::NetworkFailure(err)) => {
|
|
|
|
error!("{}", err);
|
|
|
|
return Err(ConfirmerError::NetworkFailure(err));
|
|
|
|
}
|
|
|
|
Err(ConfirmerError::DeserializeError(err)) => {
|
|
|
|
error!("Failed to deserialize the response, but the submission may have succeeded: {}", err);
|
|
|
|
return Err(ConfirmerError::DeserializeError(err));
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
warn!("submit confirmation result: {}", err);
|
|
|
|
if fail_fast || attempts >= 3 {
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
attempts += 1;
|
|
|
|
let wait = std::time::Duration::from_secs(3 * attempts);
|
|
|
|
info!(
|
|
|
|
"retrying in {} seconds (attempt {})",
|
|
|
|
wait.as_secs(),
|
|
|
|
attempts
|
|
|
|
);
|
|
|
|
std::thread::sleep(wait);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-06-23 13:36:23 -04:00
|
|
|
if self.accept_all {
|
|
|
|
info!("accepting all confirmations");
|
2023-07-24 10:29:49 -04:00
|
|
|
match submit_loop(
|
|
|
|
|| confirmer.accept_confirmations(&confirmations),
|
|
|
|
self.fail_fast,
|
|
|
|
) {
|
2023-06-29 10:33:56 -04:00
|
|
|
Ok(_) => {}
|
|
|
|
Err(err) => {
|
|
|
|
warn!("accept confirmation result: {}", err);
|
|
|
|
if self.fail_fast {
|
|
|
|
return Err(err.into());
|
2023-06-23 13:36:23 -04:00
|
|
|
}
|
2023-07-24 10:29:49 -04:00
|
|
|
any_failed = true;
|
2023-06-23 13:36:23 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if std::io::stdout().is_tty() {
|
|
|
|
let (accept, deny) = tui::prompt_confirmation_menu(confirmations)?;
|
2023-07-24 10:29:49 -04:00
|
|
|
match submit_loop(|| confirmer.accept_confirmations(&accept), self.fail_fast) {
|
2023-06-29 10:33:56 -04:00
|
|
|
Ok(_) => {}
|
|
|
|
Err(err) => {
|
|
|
|
warn!("accept confirmation result: {}", err);
|
|
|
|
if self.fail_fast {
|
|
|
|
return Err(err.into());
|
2023-06-23 13:36:23 -04:00
|
|
|
}
|
2023-07-24 10:29:49 -04:00
|
|
|
any_failed = true;
|
2023-06-23 13:36:23 -04:00
|
|
|
}
|
|
|
|
}
|
2023-07-24 10:29:49 -04:00
|
|
|
match submit_loop(|| confirmer.deny_confirmations(&deny), self.fail_fast) {
|
2023-06-29 10:33:56 -04:00
|
|
|
Ok(_) => {}
|
|
|
|
Err(err) => {
|
|
|
|
warn!("deny confirmation result: {}", err);
|
|
|
|
if self.fail_fast {
|
|
|
|
return Err(err.into());
|
2023-06-23 13:36:23 -04:00
|
|
|
}
|
2023-07-24 10:29:49 -04:00
|
|
|
any_failed = true;
|
2023-06-23 13:36:23 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
warn!("not a tty, not showing menu");
|
|
|
|
for conf in &confirmations {
|
|
|
|
println!("{}", conf.description());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if any_failed {
|
|
|
|
error!("Failed to respond to some confirmations.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
manager.save()?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|