From 68de25838c99f8c729bcab41bfc4eff5ebbba4e6 Mon Sep 17 00:00:00 2001 From: Bommels05 <69975756+Bommels05@users.noreply.github.com> Date: Wed, 5 Feb 2025 18:36:16 +0100 Subject: [PATCH] Cleanup & Fixes --- Cargo.toml | 1 + bankcli/Cargo.toml | 1 + bankcli/src/main.rs | 889 ++++++++++++++++++++++---------------------- banklib/Cargo.toml | 2 +- banklib/src/lib.rs | 380 +++---------------- rust-toolchain.toml | 2 + 6 files changed, 510 insertions(+), 765 deletions(-) create mode 100644 rust-toolchain.toml diff --git a/Cargo.toml b/Cargo.toml index 4b59302..61d64b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "3" members = [ "bankcli","banklib"] [workspace.package] diff --git a/bankcli/Cargo.toml b/bankcli/Cargo.toml index 8146260..e2b682c 100644 --- a/bankcli/Cargo.toml +++ b/bankcli/Cargo.toml @@ -17,6 +17,7 @@ clap.workspace = true url.workspace = true lalrpop-util = "0.22.1" error-stack.workspace = true +flume.workspace = true [build-dependencies] lalrpop = "0.22.1" \ No newline at end of file diff --git a/bankcli/src/main.rs b/bankcli/src/main.rs index 8010094..700520e 100644 --- a/bankcli/src/main.rs +++ b/bankcli/src/main.rs @@ -1,21 +1,18 @@ -use std::future::pending; -use banklib::{AuthenticatedMessage, BankClient, BankError, BankResult, Response}; +use banklib::config::{load_config, save_config}; +use banklib::extended::{Client, Credentials, State}; +use banklib::{AuthenticatedMessage, BankClient, BankError, BankResult, ClientAction, Response, ResponseMessage}; use clap::Parser; -use std::ops::{Add, Deref}; -use std::time::Duration; -use std::io; -use std::io::BufRead; -use std::thread::yield_now; use error_stack::{report, Report}; -use tokio::io::BufReader; +use flume::Receiver; +use std::io::stdin; +use std::ops::Add; +use std::time::Duration; +use std::thread; use tokio::select; -use tokio::task::JoinHandle; use tracing::metadata::LevelFilter; use tracing::{error, info, warn}; use tracing_subscriber::EnvFilter; use url::Url; -use banklib::config::{load_config, save_config}; -use banklib::extended::{Client, Credentials, State}; #[derive(Debug, Parser)] struct Args { @@ -62,7 +59,8 @@ async fn main() { url = arg; } else { if !config.server.last_server.is_empty() && config.general.use_last_server { - url = Url::parse(&config.server.last_server).expect("Invalid last server url in config"); + url = + Url::parse(&config.server.last_server).expect("Invalid last server url in config"); } else { error!("Cannot use last server url - You need to provide one using --url"); return; @@ -70,19 +68,31 @@ async fn main() { } let client = BankClient::connect(url.clone()).await; - let mut client = Client { client, config, state: State::default() }; + let mut client = Client { + client, + config, + state: State::default(), + }; client.config.server.last_server = url.to_string(); save_config(&client.config); if client.config.general.use_default_account { - let password = client.config.accounts.accounts.get(&client.config.accounts.default_account); + let password = client + .config + .accounts + .accounts + .get(&client.config.accounts.default_account); if let Some(password) = password { let username = client.config.accounts.default_account.clone(); let password = password.clone(); try_login(&mut client, Credentials { password, username }).await; } } else if client.config.general.use_last_account { - let password = client.config.accounts.accounts.get(&client.config.accounts.last_account); + let password = client + .config + .accounts + .accounts + .get(&client.config.accounts.last_account); if let Some(password) = password { let username = client.config.accounts.last_account.clone(); let password = password.clone(); @@ -90,382 +100,27 @@ async fn main() { } } + let lines = read_lines(); + let messages = client.client.receiver_msg().clone(); + let actions = client.client.receiver_act().clone(); + for _ in actions.try_iter() {} loop { - let line = read_line(Some(&mut client)).await; - let mut split = line.split(' '); - let command: &str = split.next().unwrap(); - - match command { - "account" => { - let argument = split.next().or(Some("")).unwrap(); - if argument == "add" { - let credentials = expect_credentials(split, &mut client, false).await; - if let Some(credentials) = credentials { - client.config.accounts.accounts.insert(credentials.username.clone(), credentials.password.clone()); - if client.config.accounts.default_account.is_empty() { - client.config.accounts.default_account = credentials.username.clone(); - } - save_config(&client.config); - if ask_confirmation("Do you want to test the account now?").await { - let old_credentials = client.state.last_credentials.clone(); - let success = try_login(&mut client, credentials.clone()).await; - if success { - setup_new_account(&mut client, credentials.clone(), old_credentials).await; - } else { - if ask_confirmation("The login was not successful - Do you want to register this account instead?").await { - let result = client.register(credentials.clone()).await; - if result.is_ok() { - let result = client.login(credentials.clone()).await; - if result.is_ok() { - setup_new_account(&mut client, credentials, old_credentials).await; - } else { - handle_error(&result); - warn!("The login was not successful but the account was most likely still registered - Please try to login again") - } - } else { - handle_error(&result); - } - } - } - } - } else { - println!("You need to enter a username and password"); - } - } else if argument == "use" { - let username: &str = split.next().or(Some("")).unwrap(); - if client.config.accounts.accounts.contains_key(username) { - let password = client.config.accounts.accounts.get(username).unwrap().clone(); - try_login(&mut client, Credentials { username: username.into(), password }).await; - } else { - println!("You need to enter a valid username") - } - } else if argument == "list" { - println!("--- Accounts ---"); - let i: u8 = 0; - for (name, _) in &client.config.accounts.accounts { - if client.config.accounts.default_account.eq(name) { - println!("{}. {} (Default)", i + 1, name); - } else { - println!("{}. {}", i + 1, name); - } - } - println!("--- Accounts ---"); - } else if argument == "remove" { - let username: &str = split.next().or(Some("")).unwrap(); - if client.config.accounts.accounts.contains_key(username) { - client.config.accounts.accounts.remove(username); - println!("Account removed"); - } else { - println!("You need to enter a valid username") - } - } else if argument == "help" { - println!("--- Available Commands ---"); - println!("account add - Add a new account"); - println!("account use [username] - Login with the default or the specified account"); - println!("account remove - Remove the account from the list"); - println!("account default - Make the account the default account"); - println!("account list - List available accounts"); - } else { - println!("Invalid subcommand! Use account help for a list of valid subcommands"); + select! { + Ok(line) = lines.recv_async() => { + if handle_line(&mut client, &lines, line).await { + break; } - } - "settings" => { - let argument = split.next().or(Some("")).unwrap(); - if argument == "set" { - let setting = split.next(); - let value = split.next(); - if setting.is_some() && value.is_some() { - { - match setting.unwrap() { - "show_motd_after_login" => { - if let Ok(value) = try_parse_bool(value) { - client.config.general.show_motd_after_login = value; - } else { - break; - } - } - "show_inbox_count_after_login" => { - if let Ok(value) = try_parse_bool(value) { - client.config.general.show_inbox_count_after_login = value; - } else { - break; - } - } - "show_pm_inbox_count_after_login" => { - if let Ok(value) = try_parse_bool(value) { - client.config.general.show_pm_inbox_count_after_login = value; - } else { - break; - } - } - "show_balance_after_payment" => { - if let Ok(value) = try_parse_bool(value) { - client.config.general.show_balance_after_payment = value; - } else { - break; - } - } - "use_default_account" => { - if let Ok(value) = try_parse_bool(value) { - client.config.general.use_default_account = value; - if value { - client.config.general.use_last_account = false; - } - } else { - break; - } - } - "use_last_account" => { - if let Ok(value) = try_parse_bool(value) { - client.config.general.use_last_account = value; - if value { - client.config.general.use_default_account = false; - } - } else { - break; - } - } - "use_last_server" => { - if let Ok(value) = try_parse_bool(value) { - client.config.general.use_last_server = value; - } else { - break; - } - } - _ => { - error!("Invalid setting") - } - } - info!("Setting changed"); - save_config(&client.config); - } - } else { - println!("You need to enter a setting and value"); - } - } else if argument == "list" { - println!("--- Settings ---"); - println!( - "show_motd_after_login: {}", - client.config.general.show_motd_after_login - ); - println!( - "show_inbox_count_after_login: {}", - client.config.general.show_inbox_count_after_login - ); - println!( - "show_pm_inbox_count_after_login: {}", - client.config.general.show_pm_inbox_count_after_login - ); - println!( - "show_balance_after_payment: {}", - client.config.general.show_balance_after_payment - ); - println!( - "use_default_account: {}", - client.config.general.use_default_account - ); - println!( - "use_last_account: {}", - client.config.general.use_last_account - ); - println!( - "use_last_server: {}", - client.config.general.use_last_server - ); - } else if argument == "help" { - println!("--- Available Commands ---"); - println!( - "settings set - Set a setting to the specified value" - ); - println!("settings list - List all settings"); - } else { - println!("Invalid subcommand! Use settings help for a list of valid subcommands"); - } - } - "register" => { - let credentials = expect_credentials(split, &mut client, true).await; - if let Some(credentials) = credentials { - let result = client.register(credentials.clone()).await; - if let Some(last_credentials) = &client.state.last_credentials { - handle_error(&client.client.set_credentials(last_credentials.username.clone(), last_credentials.password.clone())); - } - if let Err(error) = result { - match error.current_context() { - BankError::AuthenticationError => { - error!("Username is already taken"); - if ask_confirmation("Do you want to login instead?").await { - try_login(&mut client, credentials).await; - } - } - _ => print_error(&error) - } - } else { - if ask_confirmation("Do you want to login with your new account?").await { - try_login(&mut client, credentials).await; - } - } - } - } - "login" => { - let credentials = expect_credentials(split, &mut client, true).await; - if let Some(credentials) = credentials { - try_login(&mut client, credentials).await; - } - } - "balance" => { - let balance = client.get_balance().await; - if let Ok(balance) = balance { - info!("You currently have a balance of {}₿", balance); - } else { - handle_error(&balance); - } - } - "pay" => { - let destination = split.next(); - let amount = split.next(); - if destination.is_none() || amount.is_none() || amount.unwrap().parse::().is_err() || amount.unwrap().parse::().unwrap() <= 0 { - error!("You need to enter a destination username and amount greater than 0"); - } else { - let result = client.pay(destination.unwrap().into(), amount.unwrap().parse::().unwrap()).await; - handle_error(&result); - } - } - "inbox" => { - let size = client.get_inbox_size().await; - if let Ok(size) = size { - info!("--- {} Message{} ---", size, if size == 1 { "" } else { "s" }); - for i in 0..size { - let msg = client.get_inbox_msg((i + 1) as i8).await; - if let Ok((id, msg)) = msg { - info!("{id}. {msg}"); - } else { - handle_error(&msg); - } - } - info!("--- {} Message{} ---", size, if size == 1 { "" } else { "s" }); - } else { - handle_error(&size); - } - } - "pm" => { - let username = split.next(); - let message = split.next(); - if username.is_some() && message.is_some() { - let mut message: String = message.unwrap().into(); - let mut segment = split.next(); - while segment.is_some() { - message = message.add(" "); - message = message.add(segment.unwrap().into()); - segment = split.next(); - } - - let result = client.send_pm(username.unwrap().into(), message).await; - handle_error(&result); - } else if username.is_none() && username.is_none() { - let size = client.get_pm_size().await; - if let Ok(size) = size { - info!("--- {} Private Message{} ---", size, if size == 1 { "" } else { "s" }); - for i in 0..size { - let msg = client.get_pm_msg((i + 1) as i8).await; - if let Ok((id, msg)) = msg { - info!("{}. {}", id, msg); - } else { - handle_error(&msg); - } - } - info!("--- {} Private Message{} ---", size, if size == 1 { "" } else { "s" }); - } else { - handle_error(&size); - } - } else { - error!("You need to enter a username and message or nothing to view your PMs") - } - } - "delete" => { - let argument = split.next().or(Some("")).unwrap(); - let id = split.next().or(Some("")).unwrap().parse(); - if argument == "inbox" && id.is_ok() { - let result = client.delete_inbox_msg(id.unwrap()).await; - handle_error(&result); - } else if argument == "pm" && id.is_ok() { - let result = client.delete_pm_msg(id.unwrap()).await; - handle_error(&result); - } else { - println!("You need to specify inbox or pm and a message id"); - } - } - "motd" => { - let motd = client.get_motd().await; - if let Ok(motd) = motd { - println!("--- {} ---", motd) - } else { - handle_error(&motd); - } - } - "logout" => { - if !client.state.logged_in { - warn!("You are already logged out") - } else { - let result = client.logout().await; - handle_error(&result); - } - } - "exit" => { - if client.state.logged_in { - let result = client.logout().await; - handle_error(&result); - } - client.client.close("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nBefator verabschiedet sich!\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n".into()); - save_config(&client.config); - break; - } - "spam" => { - for _ in 0..100 { - for i in 0..5 { - let action: AuthenticatedMessage = match i { - 0 => AuthenticatedMessage::Authenticate, - 1 => AuthenticatedMessage::Motd, - 2 => AuthenticatedMessage::GetBalance, - 3 => AuthenticatedMessage::Register, - 4 => AuthenticatedMessage::Pay { - destination: "thc".into(), - amount: 0, - }, - _ => AuthenticatedMessage::Logout, - }; - handle_error(&client.client.send_authenticated(action, |_| true).await); - } - } - } - "help" => { - let argument = split.next().or(Some("")).unwrap(); - if argument.eq_ignore_ascii_case("extended") { - println!("--- Available extended Commands ---"); - println!("register - Register a new account and log into that account"); - println!("login [username] [password] - Log into the specified account or the account you were last logged into"); - println!("spam - Sends various requests to the Server"); - } else { - println!("--- Available Commands ---"); - println!("account - Use account help for a description of all subcommands"); - println!("balance - View your current balance"); - println!("pay - Pay someone Steam Coins"); - println!("inbox - View your inbox"); - println!("pm [destination] [message]- View your private messages or send one"); - println!("delete - Delete a message from your inbox or PMs"); - println!("motd - View the current server motd"); - println!("logout - Logout of your current account"); - println!("exit - Logout from the server and exit"); - println!("settings - Manage settings"); - println!("help [extended] - See a (extended) list of commands"); - } - } - _ => { - println!("Unknown command - Use help for a list of commands") - } + }, + Ok(message) = messages.recv_async() => { + handle_message(&mut client, message).await; + }, + Ok(action) = actions.recv_async() => { + handle_action(&mut client, action).await; + }, } } - tokio::time::sleep(Duration::from_secs(3)).await; + tokio::time::sleep(Duration::from_secs(2)).await; } async fn try_login(client: &mut Client, credentials: Credentials) -> bool { @@ -480,7 +135,7 @@ async fn try_login(client: &mut Client, credentials: Credentials) -> bool { } } } - _ => handle_error(&result) + _ => handle_error(&result), } } else { return true; @@ -488,13 +143,13 @@ async fn try_login(client: &mut Client, credentials: Credentials) -> bool { false } -async fn setup_new_account(client: &mut Client, credentials: Credentials, old_credentials: Option) { +async fn setup_new_account(client: &mut Client, rx: &Receiver, credentials: Credentials, old_credentials: Option) { if !client.config.accounts.default_account.eq(&credentials.username) { - if ask_confirmation("Do you want to make this account your default account?").await { + if ask_confirmation(rx, "Do you want to make this account your default account?").await { client.config.accounts.default_account = credentials.username.clone(); } } - if !ask_confirmation("Do you want to use this account now?").await { + if !ask_confirmation(rx, "Do you want to use this account now?").await { if let Some(old_credentials) = old_credentials { if !old_credentials.eq(&credentials) { let result = client.login(old_credentials).await; @@ -510,51 +165,29 @@ async fn setup_new_account(client: &mut Client, credentials: Credentials, old_cr } } - -async fn read_line(client: Option<&Client>) -> String { - let mut thread: Option> = None; - if let Some(client) = client { - thread = Some(tokio::spawn(async move { - /*loop { - if let Ok(message) = client.client.receiver().recv() { - match message.message { - Response::DisplayMessage { message, id, .. } => { - match id.as_str() { - "pay.received" => { - println!("{}", message); - if client.config.general.show_balance_after_payment { - let balance = client.get_balance().await; - if let Ok(balance) = balance { - info!("You now have a balance of {}₿", balance); - } else { - handle_error(&balance); - } - } - } - "pm_inbox.received" => { - println!("{}", message); - } - _ => { print_error(&report!(BankError::UnexpectedMessage)); } - } - } - _ => { print_error(&report!(BankError::UnexpectedMessage)); } - } +fn read_lines() -> Receiver { + let (tx, rx) = flume::bounded::(16); + thread::spawn(move || { + let mut input = stdin().lines(); + while let Some(line) = input.next() { + match line { + Ok(line) => { + tx.send(line).unwrap(); } - }*/ - })); - } - let mut line = String::new(); - io::stdin().read_line(&mut line).unwrap(); - let result = line.replace("\n", "").replace("\r", ""); - if let Some(thread) = thread { - thread.abort(); - } - result + Err(err) => { + error!("\n{:#?}", Report::new(err)); + break; + } + } + + } + }); + rx } -async fn ask_confirmation(prompt: &str) -> bool { +async fn ask_confirmation(rx: &Receiver, prompt: &str) -> bool { println!("{} (y/n): ", prompt); - let answer = read_line(None).await; + let answer = rx.recv_async().await.unwrap(); answer.as_str() == "y" } @@ -566,11 +199,7 @@ fn try_parse_bool(value: Option<&str>) -> Result { parsed.map_err(|_| ()) } -async fn expect_credentials( - mut split: std::str::Split<'_, char>, - client: &mut Client, - try_last_login: bool, -) -> Option { +async fn expect_credentials(mut split: std::str::Split<'_, char>, client: &mut Client, try_last_login: bool) -> Option { let username = split.next(); let password = split.next(); if username.is_none() || password.is_none() { @@ -599,5 +228,391 @@ fn handle_error(result: &BankResult) { } fn print_error(error: &Report) { - error!("\n{:?}", error); + //error!("\n{:?}", error); + error!("{}", error.as_error().to_string()); } + +async fn handle_line(client: &mut Client, rx: &Receiver, line: String) -> bool { + let mut split = line.split(' '); + let command: &str = split.next().unwrap(); + + match command { + "account" => { + let argument = split.next().or(Some("")).unwrap(); + if argument == "add" { + let credentials = expect_credentials(split, client, false).await; + if let Some(credentials) = credentials { + client.config.accounts.accounts.insert(credentials.username.clone(), credentials.password.clone()); + if client.config.accounts.default_account.is_empty() { + client.config.accounts.default_account = credentials.username.clone(); + } + save_config(&client.config); + if ask_confirmation(rx, "Do you want to test the account now?").await { + let old_credentials = client.state.last_credentials.clone(); + let success = try_login(client, credentials.clone()).await; + if success { + setup_new_account(client, rx, credentials.clone(), old_credentials).await; + } else { + if ask_confirmation(rx, "The login was not successful - Do you want to register this account instead?").await { + let result = client.register(credentials.clone()).await; + if let Some(last_credentials) = &client.state.last_credentials { + handle_error(&client.client.set_credentials(last_credentials.username.clone(), last_credentials.password.clone())); + } + if result.is_ok() { + let result = client.login(credentials.clone()).await; + if result.is_ok() { + setup_new_account(client, rx, credentials, old_credentials).await; + } else { + handle_error(&result); + warn!("The login was not successful but the account was most likely still registered - Please try to login again") + } + } else { + handle_error(&result); + } + } + } + } + } else { + println!("You need to enter a username and password"); + } + } else if argument == "use" { + let username: &str = split.next().or(Some("")).unwrap(); + if client.config.accounts.accounts.contains_key(username) { + let password = client.config.accounts.accounts.get(username).unwrap().clone(); + try_login(client, Credentials { username: username.into(), password }).await; + } else { + println!("You need to enter a valid username") + } + } else if argument == "list" { + println!("--- Accounts ---"); + let i: u8 = 0; + for (name, _) in &client.config.accounts.accounts { + if client.config.accounts.default_account.eq(name) { + println!("{}. {} (Default)", i + 1, name); + } else { + println!("{}. {}", i + 1, name); + } + } + println!("--- Accounts ---"); + } else if argument == "remove" { + let username: &str = split.next().or(Some("")).unwrap(); + if client.config.accounts.accounts.contains_key(username) { + client.config.accounts.accounts.remove(username); + println!("Account removed"); + } else { + println!("You need to enter a valid username") + } + } else if argument == "help" { + println!("--- Available Commands ---"); + println!("account add - Add a new account"); + println!("account use [username] - Login with the default or the specified account"); + println!("account remove - Remove the account from the list"); + println!("account default - Make the account the default account"); + println!("account list - List available accounts"); + } else { + println!("Invalid subcommand! Use account help for a list of valid subcommands"); + } + } + "settings" => { + let argument = split.next().or(Some("")).unwrap(); + if argument == "set" { + let setting = split.next(); + let value = split.next(); + if setting.is_some() && value.is_some() { + match setting.unwrap() { + "show_motd_after_login" => { + if let Ok(value) = try_parse_bool(value) { + client.config.general.show_motd_after_login = value; + } else { + return false; + } + } + "show_inbox_count_after_login" => { + if let Ok(value) = try_parse_bool(value) { + client.config.general.show_inbox_count_after_login = value; + } else { + return false; + } + } + "show_pm_inbox_count_after_login" => { + if let Ok(value) = try_parse_bool(value) { + client.config.general.show_pm_inbox_count_after_login = value; + } else { + return false; + } + } + "show_balance_after_payment" => { + if let Ok(value) = try_parse_bool(value) { + client.config.general.show_balance_after_payment = value; + } else { + return false; + } + } + "use_default_account" => { + if let Ok(value) = try_parse_bool(value) { + client.config.general.use_default_account = value; + if value { + client.config.general.use_last_account = false; + } + } else { + return false; + } + } + "use_last_account" => { + if let Ok(value) = try_parse_bool(value) { + client.config.general.use_last_account = value; + if value { + client.config.general.use_default_account = false; + } + } else { + return false; + } + } + "use_last_server" => { + if let Ok(value) = try_parse_bool(value) { + client.config.general.use_last_server = value; + } else { + return false; + } + } + _ => { error!("Invalid setting") } + } + info!("Setting changed"); + save_config(&client.config); + } else { + println!("You need to enter a setting and value"); + } + } else if argument == "list" { + println!("--- Settings ---"); + println!("show_motd_after_login: {}", client.config.general.show_motd_after_login); + println!("show_inbox_count_after_login: {}", client.config.general.show_inbox_count_after_login); + println!("show_pm_inbox_count_after_login: {}", client.config.general.show_pm_inbox_count_after_login); + println!("show_balance_after_payment: {}", client.config.general.show_balance_after_payment); + println!("use_default_account: {}", client.config.general.use_default_account); + println!("use_last_account: {}", client.config.general.use_last_account); + println!("use_last_server: {}", client.config.general.use_last_server); + } else if argument == "help" { + println!("--- Available Commands ---"); + println!("settings set - Set a setting to the specified value"); + println!("settings list - List all settings"); + } else { + println!("Invalid subcommand! Use settings help for a list of valid subcommands"); + } + } + "register" => { + let credentials = expect_credentials(split, client, true).await; + if let Some(credentials) = credentials { + let result = client.register(credentials.clone()).await; + if let Some(last_credentials) = &client.state.last_credentials { + handle_error(&client.client.set_credentials(last_credentials.username.clone(), last_credentials.password.clone())); + } + if let Err(error) = result { + match error.current_context() { + BankError::AuthenticationError => { + error!("Username is already taken"); + if ask_confirmation(rx, "Do you want to login instead?").await { + try_login(client, credentials).await; + } + } + _ => print_error(&error), + } + } else { + if ask_confirmation(rx, "Do you want to login with your new account?").await { + try_login(client, credentials).await; + } + } + } + } + "login" => { + let credentials = expect_credentials(split, client, true).await; + if let Some(credentials) = credentials { + try_login(client, credentials).await; + } + } + "balance" => { + let balance = client.get_balance().await; + if let Ok(balance) = balance { + info!("You currently have a balance of {}₿", balance); + } else { + handle_error(&balance); + } + } + "pay" => { + let destination = split.next(); + let amount = split.next(); + if destination.is_none() || amount.is_none() || amount.unwrap().parse::().is_err() || amount.unwrap().parse::().unwrap() <= 0 { + error!("You need to enter a destination username and amount greater than 0"); + } else { + let result = client.pay(destination.unwrap().into(), amount.unwrap().parse::().unwrap()).await; + handle_error(&result); + } + } + "inbox" => { + let size = client.get_inbox_size().await; + if let Ok(size) = size { + info!("--- {} Message{} ---", size, if size == 1 { "" } else { "s" }); + for i in 0..size { + let msg = client.get_inbox_msg((i + 1) as i8).await; + if let Ok((id, msg)) = msg { + info!("{id}. {msg}"); + } else { + handle_error(&msg); + } + } + info!("--- {} Message{} ---", size, if size == 1 { "" } else { "s" }); + } else { + handle_error(&size); + } + } + "pm" => { + let username = split.next(); + let message = split.next(); + if username.is_some() && message.is_some() { + let mut message: String = message.unwrap().into(); + let mut segment = split.next(); + while segment.is_some() { + message = message.add(" "); + message = message.add(segment.unwrap().into()); + segment = split.next(); + } + + let result = client.send_pm(username.unwrap().into(), message).await; + handle_error(&result); + } else if username.is_none() && username.is_none() { + let size = client.get_pm_size().await; + if let Ok(size) = size { + info!("--- {} Private Message{} ---", size, if size == 1 { "" } else { "s" }); + for i in 0..size { + let msg = client.get_pm_msg((i + 1) as i8).await; + if let Ok((id, msg)) = msg { + info!("{}. {}", id, msg); + } else { + handle_error(&msg); + } + } + info!("--- {} Private Message{} ---", size, if size == 1 { "" } else { "s" }); + } else { + handle_error(&size); + } + } else { + error!("You need to enter a username and message or nothing to view your PMs") + } + } + "delete" => { + let argument = split.next().or(Some("")).unwrap(); + let id = split.next().or(Some("")).unwrap().parse(); + if argument == "inbox" && id.is_ok() { + let result = client.delete_inbox_msg(id.unwrap()).await; + handle_error(&result); + } else if argument == "pm" && id.is_ok() { + let result = client.delete_pm_msg(id.unwrap()).await; + handle_error(&result); + } else { + println!("You need to specify inbox or pm and a message id"); + } + } + "motd" => { + let motd = client.get_motd().await; + if let Ok(motd) = motd { + println!("--- {} ---", motd) + } else { + handle_error(&motd); + } + } + "logout" => { + if !client.state.logged_in { + warn!("You are already logged out") + } else { + let result = client.logout().await; + handle_error(&result); + } + } + "exit" => { + if client.state.logged_in { + let result = client.logout().await; + handle_error(&result); + } + client.client.close("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nBefator verabschiedet sich!\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n".into()); + save_config(&client.config); + return true; + } + "spam" => { + for _ in 0..100 { + for i in 0..5 { + let action: AuthenticatedMessage = match i { + 0 => AuthenticatedMessage::Authenticate, + 1 => AuthenticatedMessage::Motd, + 2 => AuthenticatedMessage::GetBalance, + 3 => AuthenticatedMessage::Register, + 4 => AuthenticatedMessage::Pay { + destination: "thc".into(), + amount: 0, + }, + _ => AuthenticatedMessage::Logout, + }; + handle_error(&client.client.send_authenticated(action, |_| true).await); + } + } + } + "help" => { + let argument = split.next().or(Some("")).unwrap(); + if argument.eq_ignore_ascii_case("extended") { + println!("--- Available extended Commands ---"); + println!("register - Register a new account and log into that account"); + println!("login [username] [password] - Log into the specified account or the account you were last logged into"); + println!("spam - Sends various requests to the Server"); + } else { + println!("--- Available Commands ---"); + println!("account - Use account help for a description of all subcommands"); + println!("balance - View your current balance"); + println!("pay - Pay someone Steam Coins"); + println!("inbox - View your inbox"); + println!("pm [destination] [message]- View your private messages or send one"); + println!("delete - Delete a message from your inbox or PMs"); + println!("motd - View the current server motd"); + println!("logout - Logout of your current account"); + println!("exit - Logout from the server and exit"); + println!("settings - Manage settings"); + println!("help [extended] - See a (extended) list of commands"); + } + } + _ => { println!("Unknown command - Use help for a list of commands") } + } + + false +} + +async fn handle_message(client: &mut Client, response: ResponseMessage) { + match response.clone().message { + Response::DisplayMessage { message, id, .. } => match id.as_str() { + "pay.received" => { + println!("{}", message); + if client.config.general.show_balance_after_payment { + let balance = client.get_balance().await; + if let Ok(balance) = balance { + info!("You now have a balance of {}₿", balance); + } else { + handle_error(&balance); + } + } + } + "pm_inbox.received" => { + println!("{}", message); + } + _ => { print_error(&report!(BankError::UnexpectedMessage(response))); } + }, + _ => { print_error(&report!(BankError::UnexpectedMessage(response))); } + } +} + +async fn handle_action(client: &mut Client, action: ClientAction) { + match action { + ClientAction::Connected => { + if client.state.logged_in { + client.state.logged_in = false; + handle_error(&client.login(client.state.last_credentials.clone().unwrap()).await); + } + } + ClientAction::Disconnected => {} + } +} \ No newline at end of file diff --git a/banklib/Cargo.toml b/banklib/Cargo.toml index 747bb7a..5a02d03 100644 --- a/banklib/Cargo.toml +++ b/banklib/Cargo.toml @@ -19,6 +19,6 @@ tokio-util.workspace = true tracing.workspace = true rand.workspace = true url.workspace = true -flume.workspace =true +flume.workspace = true error-stack.workspace = true thiserror.workspace = true diff --git a/banklib/src/lib.rs b/banklib/src/lib.rs index f625f84..12cb8b4 100644 --- a/banklib/src/lib.rs +++ b/banklib/src/lib.rs @@ -23,11 +23,12 @@ pub mod config; pub struct BankClient { client: Client, - rx: Receiver, + rx_msg: Receiver, + rx_act: Receiver, handle: JoinHandle>> } -#[derive(Error, Debug)] +#[derive(Error, Debug, Clone)] pub enum BankError { #[error("Request timed out")] TimedOut, @@ -41,8 +42,8 @@ pub enum BankError { DestinationUnknown, #[error("You don't have enough Steam Coins")] NotEnoughMoney, - #[error("Unknown unexpected message received")] - UnexpectedMessage + #[error("Unknown unexpected message received {_0:?}")] + UnexpectedMessage(ResponseMessage) } pub type BankResult = error_stack::Result; @@ -52,42 +53,38 @@ impl BankClient { let url = url.into(); let client_config = ClientConfig::new(url); let random: u8 = random(); - let (tx, rx) = flume::unbounded(); + let (tx_msg, rx_msg) = flume::unbounded(); + let (tx_act, rx_act) = flume::unbounded(); let (client, future) = ezsockets::connect( move |client| Handler { client, - tx, + tx_msg, + tx_act, username: String::new(), password: String::new(), id: random as i32, receivers: VecDeque::new(), - }, - client_config, + }, client_config, ).await; let handle = tokio::spawn(future); - Self { - client, - rx, - handle, - } + Self { client, rx_msg, rx_act, handle } } - pub async fn send_authenticated( - &self, - message: AuthenticatedMessage, - accept: fn(&ResponseMessage) -> bool, - ) -> BankResult { + pub async fn send_authenticated(&self, message: AuthenticatedMessage, accept: fn(&ResponseMessage) -> bool) -> BankResult { self.check_running()?; let (tx, rx) = oneshot::channel(); self.client.call(ClientCommand::SendAuthenticatedNew {message, accept, tx}).map_err(|_| report!(BankError::InternalError))?; - tokio::time::timeout(Duration::from_secs(2), rx.into_future()).await.map_err(|_| report!(BankError::TimedOut))?.map_err(|_| report!(BankError::InternalError))?.map_err(|err| report!(err)) + tokio::time::timeout(Duration::from_secs(5), rx.into_future()).await.map_err(|_| report!(BankError::TimedOut))?.map_err(|_| report!(BankError::InternalError))?.map_err(|err| report!(err)) } pub fn set_credentials(&self, username: String, password: String) -> BankResult<()> { self.client.call(ClientCommand::ChangeCredentials {username, password}).map_err(|_| report!(BankError::InternalError)) } - pub fn receiver(&self) -> &Receiver { - &self.rx + pub fn receiver_msg(&self) -> &Receiver { + &self.rx_msg + } + pub fn receiver_act(&self) -> &Receiver { + &self.rx_act } fn check_running(&self) -> BankResult<(), BankError>{ @@ -108,22 +105,11 @@ impl BankClient { struct Handler { client: Client, - //config: Arc>, - tx: Sender, + tx_msg: Sender, + tx_act: Sender, username: String, password: String, - //last_username: String, - //last_password: String, id: i32, - //logged_in: bool, - /*message_queue: VecDeque<( - AuthenticatedMessage, - Box + Send + Unpin>, - )>, - send_time: SystemTime, - retry_count: u8, - inbox_message_count: u8, - pm_message_count: u8,*/ receivers: VecDeque<(fn(&ResponseMessage) -> bool, oneshot::Sender>)> } @@ -135,34 +121,7 @@ impl Handler { } } - /*fn last_credentials(&self) -> Credentials { - Credentials { - username: &self.last_username, - password: &self.last_password, - } - }*/ - - /*fn send_authenticated(&mut self, message: AuthenticatedMessage) { - self.send_authenticated_callback(message); - }*/ - - /*fn send_authenticated_callback( - &mut self, - message: AuthenticatedMessage, - callback: Box + Send + Unpin>, - ) { - if self.message_queue.is_empty() { - self.send_authenticated_now(message, callback); - } else { - self.message_queue.push_back((message, callback)); - } - }*/ - - fn send_authenticated( - &mut self, - message: AuthenticatedMessage, - //callback: Box + Send + Unpin>, - ) { + fn send_authenticated(&mut self, message: AuthenticatedMessage) { #[derive(Serialize)] struct WithCredentials<'a> { #[serde(flatten)] @@ -172,40 +131,20 @@ impl Handler { id: i32, } - /*if message == AuthenticatedMessage::Authenticate && self.logged_in { - self.send_authenticated_now(AuthenticatedMessage::Logout, Box::new(Box::pin(async {}))); - self.send_authenticated(message); - return; - }*/ - - //self.send_time = SystemTime::now(); - //self.message_queue.push_front((message.clone(), callback)); - debug!("Sending: {message:?}"); let credentials: Credentials = self.credentials(); - self.client - .text( - serde_json::to_string(&WithCredentials { - credentials, - message, - id: self.id, - }) - .unwrap(), - ) - .expect("Could not send authenticated message"); + self.client.text(serde_json::to_string(&WithCredentials { + credentials, + message, + id: self.id, + }).unwrap(), ).expect("Could not send authenticated message"); } - /*fn backup_auth(&mut self) { - if !self.last_username.is_empty() && !self.last_password.is_empty() { - self.username = self.last_username.clone(); - self.password = self.last_password.clone(); - self.send_authenticated(AuthenticatedMessage::Authenticate) + fn clear_receivers(&mut self, error: BankError) { + for (_, tx) in self.receivers.drain(..) { + let _ = tx.send(Err(error.clone())); } } - - fn auth_error(&self) { - warn!("Authentication error while logged in. Did the password change?"); - }*/ } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -254,7 +193,7 @@ pub enum AuthenticatedMessage { Motd, } -#[derive(Deserialize)] +#[derive(Deserialize, Clone, Debug)] #[serde(tag = "action", rename_all = "camelCase")] pub enum Response { DisplayMessage { @@ -286,7 +225,7 @@ pub enum Response { }, } -#[derive(Deserialize)] +#[derive(Deserialize, Clone, Debug)] pub struct ResponseMessage { #[serde(flatten)] pub message: Response, @@ -309,7 +248,6 @@ impl ezsockets::ClientExt for Handler { async fn on_text(&mut self, text: String) -> Result<(), Error> { debug!("Received: {text}"); - let message: ResponseMessage = serde_json::from_str(text.as_str()).expect("Error decoding message"); if message.id == self.id || message.id == -1 { @@ -325,163 +263,21 @@ impl ezsockets::ClientExt for Handler { _ => {} } - if let Some((index, (_, _))) = self.receivers.iter().enumerate().find(|(_, (accept, _))| accept(&message)) { + let mut found = false; + while let Some((index, (_, _))) = self.receivers.iter().enumerate().find(|(_, (accept, _))| accept(&message)) { let (_, tx) = self.receivers.remove(index).unwrap(); - if let Err(_) = tx.send(Ok(message.message)) { - warn!("Message receiver dropped"); + found = true; + + if let Err(message) = tx.send(Ok(message.message.clone())) { + warn!("Message receiver dropped {:?}", message); + continue; } return Ok(()); } - let _ = self.tx.send(message); - /*if self.message_queue.is_empty() { - warn!("Received unexpected message from server"); + if !found { + let _ = self.tx_msg.send(message); } - - match message.message { - Response::DisplayMessage { message, id, value } => { - if !message.is_empty() { - println!("{message}"); - } - - let config = self.config.lock().unwrap(); - let show_motd_after_login = config.general.show_motd_after_login; - let show_inbox_count_after_login = config.general.show_inbox_count_after_login; - let show_pm_inbox_count_after_login = - config.general.show_pm_inbox_count_after_login; - let show_balance_after_payment = config.general.show_balance_after_payment; - drop(config); - - match id.as_str() { - "auth.success" => { - self.id = value as i32; - self.last_username = self.username.clone(); - self.last_password = self.password.clone(); - self.logged_in = true; - - if show_motd_after_login { - self.send_authenticated(AuthenticatedMessage::Motd); - } - if show_inbox_count_after_login { - self.send_authenticated(AuthenticatedMessage::GetInbox { - message_id: -1, - }) - } - if show_pm_inbox_count_after_login { - self.send_authenticated(AuthenticatedMessage::GetPmInbox { - message_id: -1, - }) - } - - println!("(Client Id: {})", self.id); - } - "auth.fail.credentials" => { - self.backup_auth(); - } - "register.success" => { - self.send_authenticated(AuthenticatedMessage::Authenticate); - } - "register.fail.usernameTaken" => { - self.backup_auth(); - } - "logout.success" => { - self.logged_in = false; - } - "logout.fail.credentials" => { - self.auth_error(); - } - "logout.fail.notloggedin" => { - self.logged_in = false; - } - "balance.success" => {} - "balance.fail.credentials" => { - self.auth_error(); - } - "pay.fail.negative_amount" => { - panic!("Should not be able to send a negative amount") - } - "pay.fail.orig_user_unknown" => { - self.auth_error(); - } - "pay.fail.credentials" => { - self.auth_error(); - } - "pay.fail.unknown_dest" => {} - "pay.fail.not_enough_money" => { - if show_balance_after_payment { - self.send_authenticated(AuthenticatedMessage::GetBalance); - } - } - "pay.fail.unknown_error" => {} - "pay.success" => { - if show_balance_after_payment { - self.send_authenticated(AuthenticatedMessage::GetBalance); - } - } - "pay.received" => { - if show_balance_after_payment { - self.send_authenticated(AuthenticatedMessage::GetBalance); - } - } - "message_count" => { - self.inbox_message_count = value as u8; - println!( - "You have {} unread message{}", - self.inbox_message_count, - if self.inbox_message_count == 1 { - "" - } else { - "s" - } - ) - } - "pm_message_count" => { - self.pm_message_count = value as u8; - println!( - "You have {} unread private message{}", - self.pm_message_count, - if self.pm_message_count == 1 { "" } else { "s" } - ) - } - "pm_inbox.send.success" => {} - "pm_inbox.dest.unkown" => {} - "read_inbox.success" => {} - "read_inbox.fail.credentials" => { - self.auth_error(); - } - "read_pm.success" => {} - "read_pm.fail.credentials" => { - self.auth_error(); - } - _ => { - warn!("Unknown message id: {}", id) - } - } - } - Response::Inbox { - message_id, - message, - } => { - println!("{}. {}", message_id, message); - } - Response::PmInbox { - message_id, - message, - } => { - println!("{}. {}", message_id, message); - } - Response::Motd { motd } => { - println!("--- {motd} ---"); - } - } - - if !self.message_queue.is_empty() { - self.message_queue.pop_front().unwrap().1.await; - if !self.message_queue.is_empty() { - let queued = self.message_queue.pop_front().unwrap(); - self.send_authenticated_now(queued.0, queued.1); - } - }*/ } else { debug!("ignoring message for other id") } @@ -495,11 +291,7 @@ impl ezsockets::ClientExt for Handler { async fn on_call(&mut self, call: Self::Call) -> Result<(), Error> { match call { - ClientCommand::SendAuthenticatedNew { - message, - accept, - tx, - } => { + ClientCommand::SendAuthenticatedNew { message, accept, tx, } => { self.send_authenticated(message); self.receivers.push_back((accept, tx)); } @@ -510,116 +302,50 @@ impl ezsockets::ClientExt for Handler { self.username = username; self.password = password; } - /*ClientCommand::TryLastLogin => { - if self.last_username.is_empty() || self.last_password.is_empty() { - println!("You need to enter a username and password"); - } else { - self.send_authenticated(AuthenticatedMessage::Authenticate); - } - } - ClientCommand::CheckTimeout => { - if !self.message_queue.is_empty() && self.send_time.elapsed().unwrap().as_secs() > 5 - { - if self.retry_count >= 3 { - self.message_queue.pop_front(); - self.retry_count = 0; - println!("Request Timeout - All Retries failed") - } else { - self.retry_count += 1; - println!("Request Timeout - Retrying {}/3", self.retry_count); - } - if !self.message_queue.is_empty() { - let queued = self.message_queue.pop_front().unwrap(); - self.send_authenticated_now(queued.0, queued.1); - } - } - } - ClientCommand::SendWithCallback(message, callback) => { - self.send_authenticated_callback(message, callback); - } - ClientCommand::PrintInbox => { - for i in 0..self.inbox_message_count { - self.send_authenticated(AuthenticatedMessage::GetInbox { - message_id: (i + 1) as i8, - }) - } - } - ClientCommand::PrintPmInbox => { - for i in 0..self.pm_message_count { - self.send_authenticated(AuthenticatedMessage::GetPmInbox { - message_id: (i + 1) as i8, - }) - } - } - ClientCommand::TryAccount(success, error) => { - //let last_username = self.last_username.clone(); - //let last_password = self.last_password.clone(); - self.last_username = String::new(); - self.last_password = String::new(); - /*self.send_authenticated_callback(AuthenticatedMessage::Authenticate, Box::new(Box::pin(async move { - if !self.logged_in { - self.send_authenticated_callback(AuthenticatedMessage::Register, Box::new(Box::pin(async move { - if !self.logged_in { - error.await; - } else { - success.await; - } - }))); - } else { - success.await; - } - }))); - */ - }*/ } Ok(()) } async fn on_connect(&mut self) -> Result<(), Error> { info!("Successfully connected to the Server"); - if !self.username.is_empty() && !self.password.is_empty() { - self.send_authenticated(AuthenticatedMessage::Authenticate); - } + let _ = self.tx_act.send(ClientAction::Connected); Ok(()) } async fn on_connect_fail(&mut self, _error: WSError) -> Result { - info!("Disconnected, reconnecting..."); + info!("Connection failed, reconnecting..."); Ok(ClientCloseMode::Reconnect) } async fn on_close(&mut self, _frame: Option) -> Result { + self.clear_receivers(BankError::Disconnected); + let _ = self.tx_act.send(ClientAction::Disconnected); info!("Disconnected, reconnecting..."); Ok(ClientCloseMode::Reconnect) } async fn on_disconnect(&mut self) -> Result { + self.clear_receivers(BankError::Disconnected); + let _ = self.tx_act.send(ClientAction::Disconnected); info!("Disconnected, reconnecting..."); Ok(ClientCloseMode::Reconnect) } } +pub enum ClientAction { + Connected, + Disconnected +} + pub enum ClientCommand { SendAuthenticatedNew { message: AuthenticatedMessage, accept: fn(&ResponseMessage) -> bool, - tx: tokio::sync::oneshot::Sender>, + tx: oneshot::Sender>, }, SendAuthenticated(AuthenticatedMessage), - /*SendWithCallback( - AuthenticatedMessage, - Box + Send + Unpin>, - ), - PrintInbox, - PrintPmInbox,*/ ChangeCredentials { username: String, password: String, }, - /*TryLastLogin, - CheckTimeout, - TryAccount( - Box + Send + Unpin>, - Box + Send + Unpin>, - ),*/ } diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..271800c --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file