Initial Commit
This commit is contained in:
commit
340b8db1b9
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/target
|
||||||
|
.idea
|
||||||
|
*.lock
|
||||||
|
config.json
|
||||||
22
Cargo.toml
Normal file
22
Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[workspace]
|
||||||
|
members = [ "bankcli","banklib"]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
async-trait = "0.1.85"
|
||||||
|
clap = { version = "4.5.27", features = ["derive"] }
|
||||||
|
ezsockets = { version = "0.6.4", features = ["client"] }
|
||||||
|
serde = { version = "1.0.217", features = ["derive"] }
|
||||||
|
serde_json = "1.0.137"
|
||||||
|
tokio = { version = "1.43.0", features = ["full"] }
|
||||||
|
tokio-util = "0.7.13"
|
||||||
|
tracing = "0.1.41"
|
||||||
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
|
rand = "0.9.0"
|
||||||
|
url = "2.5.4"
|
||||||
|
flume = "0.11.1"
|
||||||
|
error-stack = "0.5.0"
|
||||||
|
thiserror = "2.0.11"
|
||||||
22
bankcli/Cargo.toml
Normal file
22
bankcli/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "bankcli"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[dependencies.banklib]
|
||||||
|
path = "../banklib"
|
||||||
|
features = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
clap.workspace = true
|
||||||
|
url.workspace = true
|
||||||
|
lalrpop-util = "0.22.1"
|
||||||
|
error-stack.workspace = true
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
lalrpop = "0.22.1"
|
||||||
7
bankcli/build.rs
Normal file
7
bankcli/build.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fn main() {
|
||||||
|
lalrpop::Configuration::new()
|
||||||
|
.emit_rerun_directives(true)
|
||||||
|
.set_in_dir("./src")
|
||||||
|
.process()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
603
bankcli/src/main.rs
Normal file
603
bankcli/src/main.rs
Normal file
@ -0,0 +1,603 @@
|
|||||||
|
use std::future::pending;
|
||||||
|
use banklib::{AuthenticatedMessage, BankClient, BankError, BankResult, Response};
|
||||||
|
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 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 {
|
||||||
|
#[arg(short, long)]
|
||||||
|
url: Option<Url>,
|
||||||
|
#[arg(short, long)]
|
||||||
|
debug: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
println!("Befator Inc™️ grüßt Sie!");
|
||||||
|
println!(
|
||||||
|
r#"
|
||||||
|
|
||||||
|
/$$$$$$$ /$$$$$$ /$$ /$$$$$$
|
||||||
|
| $$__ $$ /$$__ $$ | $$ |_ $$_/
|
||||||
|
| $$ \ $$ /$$$$$$ | $$ \__//$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$ | $$ /$$$$$$$ /$$$$$$$
|
||||||
|
| $$$$$$$ /$$__ $$| $$$$ |____ $$|_ $$_/ /$$__ $$ /$$__ $$ | $$ | $$__ $$ /$$_____/
|
||||||
|
| $$__ $$| $$$$$$$$| $$_/ /$$$$$$$ | $$ | $$ \ $$| $$ \__/ | $$ | $$ \ $$| $$
|
||||||
|
| $$ \ $$| $$_____/| $$ /$$__ $$ | $$ /$$| $$ | $$| $$ | $$ | $$ | $$| $$
|
||||||
|
| $$$$$$$/| $$$$$$$| $$ | $$$$$$$ | $$$$/| $$$$$$/| $$ /$$$$$$| $$ | $$| $$$$$$$
|
||||||
|
|_______/ \_______/|__/ \_______/ \___/ \______/ |__/ |______/|__/ |__/ \_______/
|
||||||
|
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(
|
||||||
|
EnvFilter::builder()
|
||||||
|
.with_default_directive(if args.debug {
|
||||||
|
LevelFilter::DEBUG.into()
|
||||||
|
} else {
|
||||||
|
LevelFilter::INFO.into()
|
||||||
|
})
|
||||||
|
.from_env_lossy(),
|
||||||
|
)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let config = load_config();
|
||||||
|
let url: Url;
|
||||||
|
if let Some(arg) = args.url {
|
||||||
|
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");
|
||||||
|
} else {
|
||||||
|
error!("Cannot use last server url - You need to provide one using --url");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let client = BankClient::connect(url.clone()).await;
|
||||||
|
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
if let Some(password) = password {
|
||||||
|
let username = client.config.accounts.last_account.clone();
|
||||||
|
let password = password.clone();
|
||||||
|
try_login(&mut client, Credentials { password, username }).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 <username> <password> - Add a new account");
|
||||||
|
println!("account use [username] - Login with the default or the specified account");
|
||||||
|
println!("account remove <username> - Remove the account from the list");
|
||||||
|
println!("account default <username> - 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 {
|
||||||
|
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 <setting> <value> - 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::<u32>().is_err() || amount.unwrap().parse::<u32>().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::<u32>().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 <username> <password> - 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 <help|add|use|list|remove> - Use account help for a description of all subcommands");
|
||||||
|
println!("balance - View your current balance");
|
||||||
|
println!("pay <username> <amount> - Pay someone Steam Coins");
|
||||||
|
println!("inbox - View your inbox");
|
||||||
|
println!("pm [destination] [message]- View your private messages or send one");
|
||||||
|
println!("delete <inbox|pm> <id> - 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 <list|set> - Manage settings");
|
||||||
|
println!("help [extended] - See a (extended) list of commands");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!("Unknown command - Use help for a list of commands")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_secs(3)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_login(client: &mut Client, credentials: Credentials) -> bool {
|
||||||
|
let result = client.login(credentials).await;
|
||||||
|
if let Err(error) = &result {
|
||||||
|
match error.current_context() {
|
||||||
|
BankError::AuthenticationError => {
|
||||||
|
if let Some(last_credentials) = &client.state.last_credentials {
|
||||||
|
let result = client.login(last_credentials.clone()).await;
|
||||||
|
if result.is_err() {
|
||||||
|
info!("Error login in with old credentials - You may need to enter a new username and password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => handle_error(&result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn setup_new_account(client: &mut Client, credentials: Credentials, old_credentials: Option<Credentials>) {
|
||||||
|
if !client.config.accounts.default_account.eq(&credentials.username) {
|
||||||
|
if ask_confirmation("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 let Some(old_credentials) = old_credentials {
|
||||||
|
if !old_credentials.eq(&credentials) {
|
||||||
|
let result = client.login(old_credentials).await;
|
||||||
|
if result.is_err() {
|
||||||
|
info!("Error logging in with old credentials - You may need to enter a new username and password");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handle_error(&client.logout().await);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handle_error(&client.logout().await);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async fn read_line(client: Option<&Client>) -> String {
|
||||||
|
let mut thread: Option<JoinHandle<_>> = 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)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ask_confirmation(prompt: &str) -> bool {
|
||||||
|
println!("{} (y/n): ", prompt);
|
||||||
|
let answer = read_line(None).await;
|
||||||
|
answer.as_str() == "y"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_parse_bool(value: Option<&str>) -> Result<bool, ()> {
|
||||||
|
let parsed = value.unwrap().parse();
|
||||||
|
if !parsed.is_ok() {
|
||||||
|
println!("The value needs to be true or false");
|
||||||
|
}
|
||||||
|
parsed.map_err(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn expect_credentials(
|
||||||
|
mut split: std::str::Split<'_, char>,
|
||||||
|
client: &mut Client,
|
||||||
|
try_last_login: bool,
|
||||||
|
) -> Option<Credentials> {
|
||||||
|
let username = split.next();
|
||||||
|
let password = split.next();
|
||||||
|
if username.is_none() || password.is_none() {
|
||||||
|
if try_last_login {
|
||||||
|
if let Some(last_credentials) = &client.state.last_credentials {
|
||||||
|
let result = client.login(last_credentials.clone()).await;
|
||||||
|
if result.is_err() {
|
||||||
|
info!("Error login in with old credentials - You may need to enter a new username and password");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("You need to enter a username and password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let username: String = username.unwrap().into();
|
||||||
|
let password: String = password.unwrap().into();
|
||||||
|
Some(Credentials { username, password })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_error<T>(result: &BankResult<T, BankError>) {
|
||||||
|
if let Err(error) = result {
|
||||||
|
print_error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_error(error: &Report<BankError>) {
|
||||||
|
error!("\n{:?}", error);
|
||||||
|
}
|
||||||
24
banklib/Cargo.toml
Normal file
24
banklib/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "banklib"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["extended", "config"]
|
||||||
|
extended = []
|
||||||
|
config = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-trait.workspace = true
|
||||||
|
clap.workspace = true
|
||||||
|
ezsockets.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
|
tokio-util.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
rand.workspace = true
|
||||||
|
url.workspace = true
|
||||||
|
flume.workspace =true
|
||||||
|
error-stack.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
74
banklib/src/config.rs
Normal file
74
banklib/src/config.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#![cfg(feature = "config")]
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fs;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub accounts: AccountConfig,
|
||||||
|
pub server: ServerConfig,
|
||||||
|
pub general: GeneralConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct AccountConfig {
|
||||||
|
pub default_account: String,
|
||||||
|
pub last_account: String,
|
||||||
|
pub accounts: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ServerConfig {
|
||||||
|
pub last_server: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct GeneralConfig {
|
||||||
|
pub use_last_server: bool,
|
||||||
|
pub use_last_account: bool,
|
||||||
|
pub use_default_account: bool,
|
||||||
|
pub show_motd_after_login: bool,
|
||||||
|
pub show_inbox_count_after_login: bool,
|
||||||
|
pub show_pm_inbox_count_after_login: bool,
|
||||||
|
pub show_balance_after_payment: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_config() -> Config {
|
||||||
|
if fs::exists("config.json").unwrap_or_else(|_| false) {
|
||||||
|
let content = fs::read_to_string("config.json");
|
||||||
|
if content.is_ok() {
|
||||||
|
let json: serde_json::Result<Config> = serde_json::from_str(content.unwrap().as_str());
|
||||||
|
if json.is_ok() {
|
||||||
|
return json.unwrap();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Config invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Config {
|
||||||
|
accounts: AccountConfig {
|
||||||
|
default_account: "".to_string(),
|
||||||
|
last_account: "".to_string(),
|
||||||
|
accounts: HashMap::new(),
|
||||||
|
},
|
||||||
|
server: ServerConfig {
|
||||||
|
last_server: "".to_string(),
|
||||||
|
},
|
||||||
|
general: GeneralConfig {
|
||||||
|
use_last_server: true,
|
||||||
|
use_last_account: true,
|
||||||
|
use_default_account: false,
|
||||||
|
show_motd_after_login: true,
|
||||||
|
show_inbox_count_after_login: true,
|
||||||
|
show_pm_inbox_count_after_login: true,
|
||||||
|
show_balance_after_payment: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_config(config: &Config) {
|
||||||
|
let string = serde_json::to_string_pretty(config).expect("Error serializing config");
|
||||||
|
fs::write("config.json", string).expect("Error saving config");
|
||||||
|
}
|
||||||
291
banklib/src/extended.rs
Normal file
291
banklib/src/extended.rs
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
#![cfg(feature = "extended")]
|
||||||
|
|
||||||
|
use error_stack::report;
|
||||||
|
use tracing::info;
|
||||||
|
use crate::{AuthenticatedMessage, BankClient, BankError, BankResult, Response, ResponseMessage};
|
||||||
|
use crate::config::{save_config, Config};
|
||||||
|
|
||||||
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
|
pub struct Credentials {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct State {
|
||||||
|
pub last_credentials: Option<Credentials>,
|
||||||
|
pub logged_in: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Client {
|
||||||
|
pub client: BankClient,
|
||||||
|
pub config: Config,
|
||||||
|
pub state: State,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! check_message_id {
|
||||||
|
($pattern:pat) => {{
|
||||||
|
fn check_message_id(message: &ResponseMessage) -> bool {
|
||||||
|
match &message.message {
|
||||||
|
Response::DisplayMessage {
|
||||||
|
message: _,
|
||||||
|
id,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
matches!(id.as_str(), $pattern)
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check_message_id
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_message(message: Response) -> (String, String, u32) {
|
||||||
|
match message {
|
||||||
|
Response::DisplayMessage { message, id, value, .. } => return (message, id, value),
|
||||||
|
_ => panic!("Unexpected message type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub async fn logout(&mut self) -> BankResult<(), BankError> {
|
||||||
|
let (message, id, _) = display_message(
|
||||||
|
self.client
|
||||||
|
.send_authenticated(
|
||||||
|
AuthenticatedMessage::Logout,
|
||||||
|
check_message_id!(
|
||||||
|
"logout.success" | "logout.fail.notloggedin" | "logout.fail.credentials"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
info!("{}", message);
|
||||||
|
match id.as_str() {
|
||||||
|
"logout.success" | "logout.fail.notloggedin" => {
|
||||||
|
self.state.logged_in = false;
|
||||||
|
}
|
||||||
|
"logout.fail.credentials" => return Err(report!(BankError::AuthenticationError)),
|
||||||
|
_ => panic!("Unexpected message"),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn login(&mut self, credentials: Credentials) -> BankResult<(), BankError> {
|
||||||
|
if self.state.logged_in {
|
||||||
|
self.logout().await?;
|
||||||
|
}
|
||||||
|
self.client.set_credentials(credentials.username.clone(), credentials.password.clone())?;
|
||||||
|
let (message, id, value) = display_message(
|
||||||
|
self.client
|
||||||
|
.send_authenticated(
|
||||||
|
AuthenticatedMessage::Authenticate,
|
||||||
|
check_message_id!("auth.success" | "auth.fail.credentials"),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
info!("{}", message);
|
||||||
|
match id.as_str() {
|
||||||
|
"auth.success" => {
|
||||||
|
self.state.last_credentials = Some(credentials.clone());
|
||||||
|
self.config.accounts.last_account = credentials.username.clone();
|
||||||
|
save_config(&self.config);
|
||||||
|
self.state.logged_in = true;
|
||||||
|
info!("(Client Id: {})", value);
|
||||||
|
|
||||||
|
if self.config.general.show_motd_after_login {
|
||||||
|
let motd = self.get_motd().await;
|
||||||
|
if let Ok(motd) = motd {
|
||||||
|
info!("--- {} ---", motd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.config.general.show_inbox_count_after_login {
|
||||||
|
let size = self.get_inbox_size().await;
|
||||||
|
if let Ok(size) = size {
|
||||||
|
info!(
|
||||||
|
"You have {} unread message{}",
|
||||||
|
size,
|
||||||
|
if size == 1 { "" } else { "s" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.config.general.show_pm_inbox_count_after_login {
|
||||||
|
let size = self.get_pm_size().await;
|
||||||
|
if let Ok(size) = size {
|
||||||
|
info!(
|
||||||
|
"You have {} unread private message{}",
|
||||||
|
size,
|
||||||
|
if size == 1 { "" } else { "s" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"auth.fail.credentials" => return Err(report!(BankError::AuthenticationError)),
|
||||||
|
_ => panic!("Unexpected message"),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn register(&mut self, credentials: Credentials) -> BankResult<(), BankError> {
|
||||||
|
self.client.set_credentials(credentials.username.clone(), credentials.password.clone())?;
|
||||||
|
let (_, id, _) = display_message(self.client.send_authenticated(
|
||||||
|
AuthenticatedMessage::Register, check_message_id!("register.success" | "register.fail.usernameTaken")).await?);
|
||||||
|
match id.as_str() {
|
||||||
|
"register.success" => Ok(()),
|
||||||
|
"register.fail.usernameTaken" => Err(report!(BankError::AuthenticationError)),
|
||||||
|
_ => panic!("Unexpected message"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_motd(&mut self) -> BankResult<String, BankError> {
|
||||||
|
let message = self
|
||||||
|
.client
|
||||||
|
.send_authenticated(AuthenticatedMessage::Motd, |response| {
|
||||||
|
match &response.message {
|
||||||
|
Response::Motd { .. } => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
match message {
|
||||||
|
Response::Motd { motd } => Ok(motd),
|
||||||
|
_ => panic!("Unexpected message type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_inbox_size(&mut self) -> BankResult<u8, BankError> {
|
||||||
|
let (_, id, value) = display_message(
|
||||||
|
self.client
|
||||||
|
.send_authenticated(
|
||||||
|
AuthenticatedMessage::GetInbox { message_id: -1 },
|
||||||
|
check_message_id!("message_count" | "inbox.fail.credentials"),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
match id.as_str() {
|
||||||
|
"message_count" => Ok(value as u8),
|
||||||
|
"inbox.fail.credentials" => Err(report!(BankError::AuthenticationError)),
|
||||||
|
_ => panic!("Unexpected message"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_inbox_msg(&mut self, id: i8) -> BankResult<(u8, String), BankError> {
|
||||||
|
let message = self.client.send_authenticated(AuthenticatedMessage::GetInbox { message_id: id }, |response| {
|
||||||
|
match &response.message {
|
||||||
|
Response::Inbox { .. } => true,
|
||||||
|
Response::DisplayMessage { message: _, id, .. } => {
|
||||||
|
id.as_str().eq("inbox.fail.credentials")
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}).await?;
|
||||||
|
match message {
|
||||||
|
Response::Inbox { message_id, message } => Ok((message_id, message)),
|
||||||
|
Response::DisplayMessage { message: _, id, .. } => {
|
||||||
|
match id.as_str() {
|
||||||
|
"inbox.fail.credentials" => Err(report!(BankError::AuthenticationError)),
|
||||||
|
_ => panic!("Unexpected message"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected message type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_inbox_msg(&mut self, message_id: u8) -> BankResult<(), BankError> {
|
||||||
|
let (message, id, _) = display_message(self.client.send_authenticated(
|
||||||
|
AuthenticatedMessage::ReadInbox { message_id: message_id as i8 }, check_message_id!("read_inbox.success" | "read_inbox.fail.credentials")).await?);
|
||||||
|
info!("{}", message);
|
||||||
|
match id.as_str() {
|
||||||
|
"read_inbox.success" => Ok(()),
|
||||||
|
"read_inbox.fail.credentials" => Err(report!(BankError::AuthenticationError)),
|
||||||
|
_ => panic!("Unexpected message"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_pm_size(&mut self) -> BankResult<u8, BankError> {
|
||||||
|
let (_, id, value) = display_message(
|
||||||
|
self.client
|
||||||
|
.send_authenticated(
|
||||||
|
AuthenticatedMessage::GetPmInbox { message_id: -1 },
|
||||||
|
check_message_id!("pm_message_count" | "pm.fail.credentials"),
|
||||||
|
)
|
||||||
|
.await?);
|
||||||
|
match id.as_str() {
|
||||||
|
"pm_message_count" => Ok(value as u8),
|
||||||
|
"pm.fail.credentials" => Err(report!(BankError::AuthenticationError)),
|
||||||
|
_ => panic!("Unexpected message"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_pm_msg(&mut self, id: i8) -> BankResult<(u8, String), BankError> {
|
||||||
|
let message = self.client.send_authenticated(AuthenticatedMessage::GetPmInbox { message_id: id }, |response| {
|
||||||
|
match &response.message {
|
||||||
|
Response::PmInbox { .. } => true,
|
||||||
|
Response::DisplayMessage { message: _, id, .. } => {
|
||||||
|
id.as_str().eq("pm.fail.credentials")
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}).await?;
|
||||||
|
match message {
|
||||||
|
Response::PmInbox { message_id, message } => Ok((message_id, message)),
|
||||||
|
Response::DisplayMessage { message: _, id, .. } => {
|
||||||
|
match id.as_str() {
|
||||||
|
"pm.fail.credentials" => Err(report!(BankError::AuthenticationError)),
|
||||||
|
_ => panic!("Unexpected message"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected message type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_pm_msg(&mut self, message_id: u8) -> BankResult<(), BankError> {
|
||||||
|
let (message, id, _) = display_message(self.client.send_authenticated(
|
||||||
|
AuthenticatedMessage::ReadPmInbox { message_id: message_id as i8 }, check_message_id!("read_pm.success" | "read_pm.fail.credentials")).await?);
|
||||||
|
info!("{}", message);
|
||||||
|
match id.as_str() {
|
||||||
|
"read_pm.success" => Ok(()),
|
||||||
|
"read_pm.fail.credentials" => Err(report!(BankError::AuthenticationError)),
|
||||||
|
_ => panic!("Unexpected message"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_balance(&mut self) -> BankResult<u32, BankError> {
|
||||||
|
let (_, id, value) = display_message(self.client.send_authenticated(
|
||||||
|
AuthenticatedMessage::GetBalance, check_message_id!("balance.success" | "balance.fail.credentials")).await?);
|
||||||
|
match id.as_str() {
|
||||||
|
"balance.success" => Ok(value),
|
||||||
|
"balance.fail.credentials" => Err(report!(BankError::AuthenticationError)),
|
||||||
|
_ => panic!("Unexpected message"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn pay(&mut self, destination: String, amount: u32) -> BankResult<(), BankError> {
|
||||||
|
let (message, id, _) = display_message(self.client.send_authenticated(
|
||||||
|
AuthenticatedMessage::Pay { destination, amount }, check_message_id!("pay.success" | "pay.fail.unknown_error" |
|
||||||
|
"pay.fail.not_enough_money" | "pay.fail.credentials" | "pay.fail.unknown_dest" | "pay.fail.orig_user_unknown" | "pay.fail.negative_amount")).await?);
|
||||||
|
info!("{}", message);
|
||||||
|
match id.as_str() {
|
||||||
|
"pay.success" => Ok(()),
|
||||||
|
"pay.fail.not_enough_money" => Err(report!(BankError::NotEnoughMoney)),
|
||||||
|
"pay.fail.unknown_dest" => Err(report!(BankError::DestinationUnknown)),
|
||||||
|
"pay.fail.credentials" => Err(report!(BankError::AuthenticationError)),
|
||||||
|
"pay.fail.orig_user_unknown" => Err(report!(BankError::AuthenticationError)),
|
||||||
|
"pay.fail.negative_amount" => Err(report!(BankError::InternalError)),
|
||||||
|
"pay.fail.unknown_error" => Err(report!(BankError::InternalError)),
|
||||||
|
_ => panic!("Unexpected message"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_pm(&mut self, destination: String, message: String) -> BankResult<(), BankError> {
|
||||||
|
let (message, id, _) = display_message(self.client.send_authenticated(
|
||||||
|
AuthenticatedMessage::SendPm { destination, message }, check_message_id!("pm_inbox.send.success" | "pm_inbox.dest.unknown" | "pm_inbox.fail.credentials")).await?);
|
||||||
|
info!("{}", message);
|
||||||
|
match id.as_str() {
|
||||||
|
"pm_inbox.send.success" => Ok(()),
|
||||||
|
"pm_inbox.dest.unknown" => Err(report!(BankError::DestinationUnknown)),
|
||||||
|
"pm_inbox.fail.credentials" => Err(report!(BankError::AuthenticationError)),
|
||||||
|
_ => panic!("Unexpected message"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
625
banklib/src/lib.rs
Normal file
625
banklib/src/lib.rs
Normal file
@ -0,0 +1,625 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use ezsockets::{
|
||||||
|
client::ClientCloseMode, Client, ClientConfig, CloseCode, CloseFrame, Error, WSError,
|
||||||
|
};
|
||||||
|
use rand::random;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::future::IntoFuture;
|
||||||
|
use std::time::Duration;
|
||||||
|
use error_stack::report;
|
||||||
|
use flume::{Receiver, Sender};
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
use tracing::{debug, info, warn};
|
||||||
|
use thiserror::Error;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[cfg(feature = "extended")]
|
||||||
|
pub mod extended;
|
||||||
|
#[cfg(feature = "config")]
|
||||||
|
pub mod config;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct BankClient {
|
||||||
|
client: Client<Handler>,
|
||||||
|
rx: Receiver<ResponseMessage>,
|
||||||
|
handle: JoinHandle<Result<(), Box<dyn std::error::Error + Send + Sync>>>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum BankError {
|
||||||
|
#[error("Request timed out")]
|
||||||
|
TimedOut,
|
||||||
|
#[error("Disconnected from Server")]
|
||||||
|
Disconnected,
|
||||||
|
#[error("Unexpected internal error")]
|
||||||
|
InternalError,
|
||||||
|
#[error("Authentication error while logged in - Did the password change?")]
|
||||||
|
AuthenticationError,
|
||||||
|
#[error("Destination user does not exist")]
|
||||||
|
DestinationUnknown,
|
||||||
|
#[error("You don't have enough Steam Coins")]
|
||||||
|
NotEnoughMoney,
|
||||||
|
#[error("Unknown unexpected message received")]
|
||||||
|
UnexpectedMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type BankResult<T, E = BankError> = error_stack::Result<T, E>;
|
||||||
|
|
||||||
|
impl BankClient {
|
||||||
|
pub async fn connect(url: impl Into<Url>) -> Self {
|
||||||
|
let url = url.into();
|
||||||
|
let client_config = ClientConfig::new(url);
|
||||||
|
let random: u8 = random();
|
||||||
|
let (tx, rx) = flume::unbounded();
|
||||||
|
let (client, future) = ezsockets::connect(
|
||||||
|
move |client| Handler {
|
||||||
|
client,
|
||||||
|
tx,
|
||||||
|
username: String::new(),
|
||||||
|
password: String::new(),
|
||||||
|
id: random as i32,
|
||||||
|
receivers: VecDeque::new(),
|
||||||
|
},
|
||||||
|
client_config,
|
||||||
|
).await;
|
||||||
|
let handle = tokio::spawn(future);
|
||||||
|
Self {
|
||||||
|
client,
|
||||||
|
rx,
|
||||||
|
handle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn send_authenticated(
|
||||||
|
&self,
|
||||||
|
message: AuthenticatedMessage,
|
||||||
|
accept: fn(&ResponseMessage) -> bool,
|
||||||
|
) -> BankResult<Response> {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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<ResponseMessage> {
|
||||||
|
&self.rx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_running(&self) -> BankResult<(), BankError>{
|
||||||
|
if !self.handle.is_finished() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(report!(BankError::InternalError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(&self, reason: String) {
|
||||||
|
self.client.close(Some(CloseFrame {
|
||||||
|
code: CloseCode::Normal,
|
||||||
|
reason,
|
||||||
|
})).expect("Error closing websocket");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Handler {
|
||||||
|
client: Client<Handler>,
|
||||||
|
//config: Arc<Mutex<Config>>,
|
||||||
|
tx: Sender<ResponseMessage>,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
//last_username: String,
|
||||||
|
//last_password: String,
|
||||||
|
id: i32,
|
||||||
|
//logged_in: bool,
|
||||||
|
/*message_queue: VecDeque<(
|
||||||
|
AuthenticatedMessage,
|
||||||
|
Box<dyn Future<Output = ()> + Send + Unpin>,
|
||||||
|
)>,
|
||||||
|
send_time: SystemTime,
|
||||||
|
retry_count: u8,
|
||||||
|
inbox_message_count: u8,
|
||||||
|
pm_message_count: u8,*/
|
||||||
|
receivers: VecDeque<(fn(&ResponseMessage) -> bool, oneshot::Sender<Result<Response, BankError>>)>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler {
|
||||||
|
fn credentials(&self) -> Credentials {
|
||||||
|
Credentials {
|
||||||
|
username: &self.username,
|
||||||
|
password: &self.password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*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<dyn Future<Output = ()> + 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<dyn Future<Output = ()> + Send + Unpin>,
|
||||||
|
) {
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct WithCredentials<'a> {
|
||||||
|
#[serde(flatten)]
|
||||||
|
credentials: Credentials<'a>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
message: AuthenticatedMessage,
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*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 auth_error(&self) {
|
||||||
|
warn!("Authentication error while logged in. Did the password change?");
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
struct Credentials<'a> {
|
||||||
|
#[serde(rename = "value1")]
|
||||||
|
username: &'a str,
|
||||||
|
#[serde(rename = "value2")]
|
||||||
|
password: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, PartialEq, Clone)]
|
||||||
|
#[serde(tag = "action", rename_all = "snake_case")]
|
||||||
|
pub enum AuthenticatedMessage {
|
||||||
|
Authenticate,
|
||||||
|
Register,
|
||||||
|
Logout,
|
||||||
|
GetBalance,
|
||||||
|
Pay {
|
||||||
|
#[serde(rename = "value3")]
|
||||||
|
destination: String,
|
||||||
|
#[serde(rename = "value4")]
|
||||||
|
amount: u32,
|
||||||
|
},
|
||||||
|
GetInbox {
|
||||||
|
#[serde(rename = "value3")]
|
||||||
|
message_id: i8,
|
||||||
|
},
|
||||||
|
GetPmInbox {
|
||||||
|
#[serde(rename = "value3")]
|
||||||
|
message_id: i8,
|
||||||
|
},
|
||||||
|
ReadInbox {
|
||||||
|
#[serde(rename = "value3")]
|
||||||
|
message_id: i8,
|
||||||
|
},
|
||||||
|
ReadPmInbox {
|
||||||
|
#[serde(rename = "value3")]
|
||||||
|
message_id: i8,
|
||||||
|
},
|
||||||
|
SendPm {
|
||||||
|
#[serde(rename = "value3")]
|
||||||
|
destination: String,
|
||||||
|
#[serde(rename = "value4")]
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
|
Motd,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(tag = "action", rename_all = "camelCase")]
|
||||||
|
pub enum Response {
|
||||||
|
DisplayMessage {
|
||||||
|
#[serde(rename = "value1")]
|
||||||
|
message: String,
|
||||||
|
#[serde(rename = "value2")]
|
||||||
|
id: String,
|
||||||
|
#[serde(rename = "value3", default = "default_u32")]
|
||||||
|
value: u32,
|
||||||
|
#[serde(rename = "value4", default = "String::new")]
|
||||||
|
value2: String,
|
||||||
|
},
|
||||||
|
Inbox {
|
||||||
|
#[serde(rename = "value1")]
|
||||||
|
message_id: u8,
|
||||||
|
#[serde(rename = "value2")]
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
|
#[serde(rename = "pm_inbox")]
|
||||||
|
PmInbox {
|
||||||
|
#[serde(rename = "value1")]
|
||||||
|
message_id: u8,
|
||||||
|
#[serde(rename = "value2")]
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
|
Motd {
|
||||||
|
#[serde(rename = "value1")]
|
||||||
|
motd: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ResponseMessage {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub message: Response,
|
||||||
|
#[serde(default = "default_i32")]
|
||||||
|
id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_i32() -> i32 {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_u32() -> u32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ezsockets::ClientExt for Handler {
|
||||||
|
type Call = ClientCommand;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
match &message.message {
|
||||||
|
Response::DisplayMessage { message: _, id, value, .. } => {
|
||||||
|
match id.as_str() {
|
||||||
|
"auth.success" => {
|
||||||
|
self.id = value.clone() as i32;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if 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");
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let _ = self.tx.send(message);
|
||||||
|
|
||||||
|
/*if self.message_queue.is_empty() {
|
||||||
|
warn!("Received unexpected message from server");
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_binary(&mut self, _: Vec<u8>) -> Result<(), Error> {
|
||||||
|
panic!("binary?? what is that?")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_call(&mut self, call: Self::Call) -> Result<(), Error> {
|
||||||
|
match call {
|
||||||
|
ClientCommand::SendAuthenticatedNew {
|
||||||
|
message,
|
||||||
|
accept,
|
||||||
|
tx,
|
||||||
|
} => {
|
||||||
|
self.send_authenticated(message);
|
||||||
|
self.receivers.push_back((accept, tx));
|
||||||
|
}
|
||||||
|
ClientCommand::SendAuthenticated(message) => {
|
||||||
|
self.send_authenticated(message);
|
||||||
|
}
|
||||||
|
ClientCommand::ChangeCredentials { username, password } => {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_connect_fail(&mut self, _error: WSError) -> Result<ClientCloseMode, Error> {
|
||||||
|
info!("Disconnected, reconnecting...");
|
||||||
|
Ok(ClientCloseMode::Reconnect)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_close(&mut self, _frame: Option<CloseFrame>) -> Result<ClientCloseMode, Error> {
|
||||||
|
info!("Disconnected, reconnecting...");
|
||||||
|
Ok(ClientCloseMode::Reconnect)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_disconnect(&mut self) -> Result<ClientCloseMode, Error> {
|
||||||
|
info!("Disconnected, reconnecting...");
|
||||||
|
Ok(ClientCloseMode::Reconnect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ClientCommand {
|
||||||
|
SendAuthenticatedNew {
|
||||||
|
message: AuthenticatedMessage,
|
||||||
|
accept: fn(&ResponseMessage) -> bool,
|
||||||
|
tx: tokio::sync::oneshot::Sender<Result<Response, BankError>>,
|
||||||
|
},
|
||||||
|
SendAuthenticated(AuthenticatedMessage),
|
||||||
|
/*SendWithCallback(
|
||||||
|
AuthenticatedMessage,
|
||||||
|
Box<dyn Future<Output = ()> + Send + Unpin>,
|
||||||
|
),
|
||||||
|
PrintInbox,
|
||||||
|
PrintPmInbox,*/
|
||||||
|
ChangeCredentials {
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
},
|
||||||
|
/*TryLastLogin,
|
||||||
|
CheckTimeout,
|
||||||
|
TryAccount(
|
||||||
|
Box<dyn Future<Output = ()> + Send + Unpin>,
|
||||||
|
Box<dyn Future<Output = ()> + Send + Unpin>,
|
||||||
|
),*/
|
||||||
|
}
|
||||||
0
src/main.rs
Normal file
0
src/main.rs
Normal file
Loading…
x
Reference in New Issue
Block a user