diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..be06ae2 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,134 @@ +use std::str::FromStr; +use clap::Parser; + +#[derive(Debug, Clone, Parser)] +#[clap(author, version, about = "Generate Steam 2FA codes and confirm Steam trades from the command line.", long_about = None)] +pub(crate) struct Args { + #[clap(short, long, help = "Steam username, case-sensitive.", long_help = "Select the account you want by steam username. Case-sensitive. By default, the first account in the manifest is selected.")] + pub username: Option, + #[clap(short, long, help = "Select all accounts in the manifest.")] + pub all: bool, + /// The path to the maFiles directory. + #[clap(short, long, default_value = "~/.config/steamguard-cli/maFiles", help = "Specify which folder your maFiles are in. This should be a path to a folder that contains manifest.json.")] + pub mafiles_path: String, + #[clap(short, long, help = "Specify your encryption passkey.")] + pub passkey: Option, + #[clap(short, long, default_value_t=Verbosity::Info, help = "Set the log level.")] + pub verbosity: Verbosity, + + #[clap(subcommand)] + pub sub: Option, +} + +#[derive(Debug, Clone, Parser)] +pub(crate) enum Subcommands { + Debug { + #[clap(long)] + demo_conf_menu: bool + }, + // Completions { + // TODO: Add completions + // }, + #[clap(about = "Interactive interface for trade confirmations")] + Trade { + #[clap(short, long, help = "Accept all open trade confirmations. Does not open interactive interface.")] + accept_all: bool, + #[clap(short, long, help = "If submitting a confirmation response fails, exit immediately.")] + fail_fast: bool, + }, + #[clap(about = "Set up a new account with steamguard-cli")] + Setup { + #[clap(short, long, from_global, help = "Steam username, case-sensitive.")] + username: Option, + }, + #[clap(about = "Import an account with steamguard already set up")] + Import { + #[clap(long, help = "Paths to one or more maFiles, eg. \"./gaben.maFile\"")] + files: Vec, + }, + #[clap(about = "Remove the authenticator from an account.")] + Remove { + #[clap(short, long, from_global, help = "Steam username, case-sensitive.")] + username: String, + }, + #[clap(about = "Encrypt all maFiles")] + Encrypt, + #[clap(about = "Decrypt all maFiles")] + Decrypt, +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum Verbosity { + Error = 0, + Warn = 1, + Info = 2, + Debug = 3, + Trace = 4, +} + +impl std::fmt::Display for Verbosity { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}", match self { + Verbosity::Error => "error", + Verbosity::Warn => "warn", + Verbosity::Info => "info", + Verbosity::Debug => "debug", + Verbosity::Trace => "trace", + })) + } +} + +impl FromStr for Verbosity { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "error" => Ok(Verbosity::Error), + "warn" => Ok(Verbosity::Warn), + "info" => Ok(Verbosity::Info), + "debug" => Ok(Verbosity::Debug), + "trace" => Ok(Verbosity::Trace), + _ => Err(anyhow!("Invalid verbosity level: {}", s)), + } + } +} + +pub(crate) struct ArgsSetup { + pub username: Option, +} + +impl From for ArgsSetup { + fn from(sub: Subcommands) -> Self { + match sub { + Subcommands::Setup { username } => Self { username }, + _ => panic!("ArgsSetup::from() called with non-Setup subcommand"), + } + } +} + +pub(crate) struct ArgsImport { + pub files: Vec, +} + +impl From for ArgsImport { + fn from(sub: Subcommands) -> Self { + match sub { + Subcommands::Import { files } => Self { files }, + _ => panic!("ArgsImport::from() called with non-Import subcommand"), + } + } +} + +pub(crate) struct ArgsTrade { + pub accept_all: bool, + pub fail_fast: bool, +} + +impl From for ArgsTrade { + fn from(sub: Subcommands) -> Self { + match sub { + Subcommands::Trade { accept_all, fail_fast } => Self { accept_all, fail_fast }, + _ => panic!("ArgsTrade::from() called with non-Trade subcommand"), + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 7fc25ea..696b506 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ extern crate rpassword; use clap::{crate_version, App, Arg, ArgMatches, Parser, Subcommand}; use log::*; -use std::str::FromStr; use std::{ io::{stdout, Write}, path::Path, @@ -24,143 +23,12 @@ extern crate dirs; extern crate proptest; extern crate ring; mod accountmanager; +mod cli; mod demos; mod encryption; mod errors; mod tui; -#[derive(Debug, Clone, Parser)] -#[clap(author, version, about = "Generate Steam 2FA codes and confirm Steam trades from the command line.", long_about = None)] -struct Args { - #[clap(short, long, help = "Steam username, case-sensitive.", long_help = "Select the account you want by steam username. Case-sensitive. By default, the first account in the manifest is selected.")] - username: Option, - #[clap(short, long, help = "Select all accounts in the manifest.")] - all: bool, - /// The path to the maFiles directory. - #[clap(short, long, default_value = "~/.config/steamguard-cli/maFiles", help = "Specify which folder your maFiles are in. This should be a path to a folder that contains manifest.json.")] - mafiles_path: String, - #[clap(short, long, help = "Specify your encryption passkey.")] - passkey: Option, - #[clap(short, long, default_value_t=Verbosity::Info, help = "Set the log level.")] - verbosity: Verbosity, - - #[clap(subcommand)] - sub: Option, -} - -#[derive(Debug, Clone, Parser)] -enum Subcommands { - Debug { - #[clap(long)] - demo_conf_menu: bool - }, - // Completions { - // TODO: Add completions - // }, - #[clap(about = "Interactive interface for trade confirmations")] - Trade { - #[clap(short, long, help = "Accept all open trade confirmations. Does not open interactive interface.")] - accept_all: bool, - #[clap(short, long, help = "If submitting a confirmation response fails, exit immediately.")] - fail_fast: bool, - }, - #[clap(about = "Set up a new account with steamguard-cli")] - Setup { - #[clap(short, long, from_global, help = "Steam username, case-sensitive.")] - username: Option, - }, - #[clap(about = "Import an account with steamguard already set up")] - Import { - #[clap(long, help = "Paths to one or more maFiles, eg. \"./gaben.maFile\"")] - files: Vec, - }, - #[clap(about = "Remove the authenticator from an account.")] - Remove { - #[clap(short, long, from_global, help = "Steam username, case-sensitive.")] - username: String, - }, - #[clap(about = "Encrypt all maFiles")] - Encrypt, - #[clap(about = "Decrypt all maFiles")] - Decrypt, -} - -#[derive(Debug, Clone, Copy)] -enum Verbosity { - Error = 0, - Warn = 1, - Info = 2, - Debug = 3, - Trace = 4, -} - -impl std::fmt::Display for Verbosity { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}", match self { - Verbosity::Error => "error", - Verbosity::Warn => "warn", - Verbosity::Info => "info", - Verbosity::Debug => "debug", - Verbosity::Trace => "trace", - })) - } -} - -impl FromStr for Verbosity { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - match s { - "error" => Ok(Verbosity::Error), - "warn" => Ok(Verbosity::Warn), - "info" => Ok(Verbosity::Info), - "debug" => Ok(Verbosity::Debug), - "trace" => Ok(Verbosity::Trace), - _ => Err(anyhow!("Invalid verbosity level: {}", s)), - } - } -} - -struct ArgsSetup { - username: Option, -} - -impl From for ArgsSetup { - fn from(sub: Subcommands) -> Self { - match sub { - Subcommands::Setup { username } => Self { username }, - _ => panic!("ArgsSetup::from() called with non-Setup subcommand"), - } - } -} - -struct ArgsImport { - files: Vec, -} - -impl From for ArgsImport { - fn from(sub: Subcommands) -> Self { - match sub { - Subcommands::Import { files } => Self { files }, - _ => panic!("ArgsImport::from() called with non-Import subcommand"), - } - } -} - -struct ArgsTrade { - accept_all: bool, - fail_fast: bool, -} - -impl From for ArgsTrade { - fn from(sub: Subcommands) -> Self { - match sub { - Subcommands::Trade { accept_all, fail_fast } => Self { accept_all, fail_fast }, - _ => panic!("ArgsTrade::from() called with non-Trade subcommand"), - } - } -} - fn cli() -> App<'static> { App::new("steamguard-cli") .version(crate_version!()) @@ -276,7 +144,7 @@ fn main() { } fn run() -> anyhow::Result<()> { - let new_args = Args::parse(); + let new_args = cli::Args::parse(); println!("{:?}", new_args); let matches = cli().get_matches(); @@ -288,25 +156,23 @@ fn run() -> anyhow::Result<()> { .init() .unwrap(); - if let Some(subcmd) = new_args.sub { - match subcmd { - Subcommand::Debug{demo_conf_menu} => { - if demo_conf_menu { - demos::demo_confirmation_menu(); - } - return Ok(()); - }, - // Subcommand::Completions{shell} => { - // // cli().gen_completions_to( - // // "steamguard", - // // Shell::from_str(completion_matches.value_of("shell").unwrap()).unwrap(), - // // &mut std::io::stdout(), - // // ); - // return Ok(()); - // }, - _ => {}, - }; - } + match new_args.sub { + Some(cli::Subcommands::Debug{demo_conf_menu}) => { + if demo_conf_menu { + demos::demo_confirmation_menu(); + } + return Ok(()); + }, + // Subcommand::Completions{shell} => { + // // cli().gen_completions_to( + // // "steamguard", + // // Shell::from_str(completion_matches.value_of("shell").unwrap()).unwrap(), + // // &mut std::io::stdout(), + // // ); + // return Ok(()); + // }, + _ => {}, + }; let mafiles_dir = if matches.occurrences_of("mafiles-path") > 0 { matches.value_of("mafiles-path").unwrap().into() @@ -367,16 +233,14 @@ fn run() -> anyhow::Result<()> { } } - if let Some(subcmd) = new_args.sub { - match subcmd { - Subcommands::Setup{ username } => { - do_subcmd_setup(new_args.sub.unwrap().into(), &mut manifest)?; - }, - Subcommands::Import { files } => {todo!()}, - Subcommands::Encrypt {} => {todo!()}, - Subcommands::Decrypt {} => {todo!()}, - _ => {}, - } + match new_args.sub { + Some(cli::Subcommands::Setup{ username }) => { + do_subcmd_setup(new_args.sub.unwrap().into(), &mut manifest)?; + }, + Some(cli::Subcommands::Import { files }) => {todo!()}, + Some(cli::Subcommands::Encrypt {}) => {todo!()}, + Some(cli::Subcommands::Decrypt {}) => {todo!()}, + _ => {}, } if matches.is_present("setup") { @@ -458,7 +322,7 @@ fn run() -> anyhow::Result<()> { if let Some(subcmd) = new_args.sub { match subcmd { - Subcommands::Trade{ accept_all, fail_fast } => { + cli::Subcommands::Trade{ accept_all, fail_fast } => { for a in selected_accounts.iter_mut() { let mut account = a.lock().unwrap(); @@ -535,7 +399,7 @@ fn run() -> anyhow::Result<()> { manifest.save()?; }, - Subcommands::Remove { username } => { + cli::Subcommands::Remove { username } => { println!( "This will remove the mobile authenticator from {} accounts: {}", selected_accounts.len(), @@ -735,13 +599,16 @@ fn get_mafiles_dir() -> String { return paths[0].to_str().unwrap().into(); } -fn do_subcmd_setup(args: ArgsSetup, manifest: &mut accountmanager::Manifest) -> anyhow::Result<()> { +fn do_subcmd_setup(args: cli::ArgsSetup, manifest: &mut accountmanager::Manifest) -> anyhow::Result<()> { println!("Log in to the account that you want to link to steamguard-cli"); print!("Username: "); - let username = args.username.unwrap_or(tui::prompt()); - if args.username.is_some() { - println!("{}", username); - } + let username = if args.username.is_some() { + let u = args.username.unwrap(); + println!("{}", u); + u + } else { + tui::prompt() + }; let account_name = username.clone(); if manifest.account_exists(&username) { bail!(