From a8b3f7f8714f580b19f4bc2808e8cc2bfbe6956f Mon Sep 17 00:00:00 2001 From: Bommels05 <69975756+Bommels05@users.noreply.github.com> Date: Sat, 8 Feb 2025 17:27:44 +0100 Subject: [PATCH] Advanced PMs --- Cargo.toml | 5 +- bankcli/Cargo.toml | 1 + bankcli/src/main.rs | 220 +++++++++++++++++++++--- banklib/Cargo.toml | 1 + banklib/src/config.rs | 28 ++- banklib/src/extended.rs | 365 +++++++++++++++++++++++++++++----------- banklib/src/lib.rs | 124 ++++++++++++-- src/main.rs | 0 8 files changed, 607 insertions(+), 137 deletions(-) delete mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 61d64b4..e08960f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "3" -members = [ "bankcli","banklib"] +members = ["bankcli","banklib"] [workspace.package] version = "0.0.1" @@ -20,4 +20,5 @@ rand = "0.9.0" url = "2.5.4" flume = "0.11.1" error-stack = "0.5.0" -thiserror = "2.0.11" \ No newline at end of file +thiserror = "2.0.11" +chrono = { version = "0.4.39", features = ["serde"] } \ No newline at end of file diff --git a/bankcli/Cargo.toml b/bankcli/Cargo.toml index e2b682c..23fb351 100644 --- a/bankcli/Cargo.toml +++ b/bankcli/Cargo.toml @@ -18,6 +18,7 @@ url.workspace = true lalrpop-util = "0.22.1" error-stack.workspace = true flume.workspace = true +chrono.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 700520e..b6ff991 100644 --- a/bankcli/src/main.rs +++ b/bankcli/src/main.rs @@ -1,6 +1,6 @@ use banklib::config::{load_config, save_config}; -use banklib::extended::{Client, Credentials, State}; -use banklib::{AuthenticatedMessage, BankClient, BankError, BankResult, ClientAction, Response, ResponseMessage}; +use banklib::extended::{Client, Credentials, MessageHistoryEntry, State}; +use banklib::{AuthenticatedMessage, BankClient, BankError, BankResult, ClientAction, MsgType, Response, ResponseMessage}; use clap::Parser; use error_stack::{report, Report}; use flume::Receiver; @@ -269,6 +269,13 @@ async fn handle_line(client: &mut Client, rx: &Receiver, line: String) - } else { handle_error(&result); } + } else { + if !ask_confirmation(rx, "Do you still want to add the account?").await { + client.config.accounts.accounts.remove(&credentials.username); + if client.config.accounts.default_account.eq(&credentials.username) { + client.config.accounts.default_account = String::new(); + } + } } } } @@ -298,6 +305,9 @@ async fn handle_line(client: &mut Client, rx: &Receiver, line: String) - let username: &str = split.next().or(Some("")).unwrap(); if client.config.accounts.accounts.contains_key(username) { client.config.accounts.accounts.remove(username); + if client.config.accounts.default_account.eq(username) { + client.config.accounts.default_account = String::new(); + } println!("Account removed"); } else { println!("You need to enter a valid username") @@ -479,34 +489,193 @@ async fn handle_line(client: &mut Client, rx: &Receiver, line: String) - 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" }); + let use_fake_inbox = if client.config.acknowledgements.use_fake_pm_inbox { + client.config.general.use_fake_pm_inbox } else { - handle_error(&size); + let use_fake_inbox = ask_confirmation(rx, "To correctly record the first time these messages were seen the messages need to be deleted from the Server. This may prevent you from viewing these private messages using other clients. The messages will still be marked as unread in this client.").await; + if ask_confirmation(rx, "Do you want this answer to be saved?").await { + client.config.acknowledgements.use_fake_pm_inbox = true; + client.config.general.use_fake_pm_inbox = use_fake_inbox; + } + use_fake_inbox + }; + if use_fake_inbox { + let result = client.update_new_pm_history().await; + if result.is_err() { + handle_error(&result); + return false; + } + } + + let new = client.get_new_pms().await; + if let Ok(new) = new { + let history = client.get_new_pm_history().await; + if let Ok(history) = history { + let size = new.len() + history.len(); + info!("--- {} New Private Message{} ---", size, if size == 1 { "" } else { "s" }); + for (id, message) in new { + info!("*{}. [?]{}", id, message); + } + let mut i = 1; + for msg in history.clone() { + info!("{}. [{}]{}: {}", i, msg.time.format("%M:%H %d.%m.%y"), msg.sender, msg.message); + i += 1; + } + info!("*: Message not saved in the history"); + info!("--- {} New Private Message{} ---", size, if size == 1 { "" } else { "s" }); + if ask_confirmation(rx, "Do you want to mark all messages as read?").await { + handle_error(&client.move_all_new_pms().await); + } + } else { + handle_error(&history); + } + } else { + handle_error(&new); } } else { error!("You need to enter a username and message or nothing to view your PMs") } } + "chat" => { + let username = split.next().or(Some("")).unwrap(); + if !username.is_empty() { + let result = client.get_pm_history(username.into()).await; + if let Ok(history) = result { + if client.config.general.use_fake_pm_inbox { + handle_error(&client.update_new_pm_history().await); + } else { + warn!("You do not have advanced storing of unread private messages enabled. These unread messages will not appear in the context here.") + } + let result = client.get_new_pm_history().await; + if let Ok(new_history) = result { + info!("--- Private Messages with {} ---", username); + info!("Type \"exit\" to exit"); + for i in 0..client.config.general.chat_context_window { + if history.len() > i as usize { + let msg: MessageHistoryEntry = history.get(i as usize).unwrap().clone(); + info!("[{}]{}: {}", msg.time.format("%H:%M:%S %d.%m.%y"), msg.sender, msg.message) + } + } + for msg in &new_history { + if msg.sender.eq_ignore_ascii_case(&username) { + info!("*[{}]{}: {}", msg.time.format("%H:%M %d.%m.%y"), msg.sender, msg.message) + } + } + if !new_history.is_empty() && ask_confirmation(rx, "Do you want to mark all unread messages as read now?").await { + handle_error(&client.move_all_new_pms().await); + } + + let messages = client.client.receiver_msg().clone(); + let actions = client.client.receiver_act().clone(); + loop { + select! { + Ok(line) = rx.recv_async() => { + if line.eq_ignore_ascii_case("exit") { + info!("Successfully left the chat"); + break; + } else { + handle_error(&client.send_pm(username.into(), line).await); + } + }, + Ok(message) = messages.recv_async() => { + match message.message { + Response::DisplayMessage { message: _, msg_type } => match msg_type { + MsgType::PmReceived { sender, message: _ } => { + if sender.eq_ignore_ascii_case(&username) { + let result = client.move_newest_pm_to_history().await; + if let Ok(msg) = result { + info!("[{}]{}: {}", msg.time.format("%H:%M %d.%m.%y"), msg.sender, msg.message) + } else { + handle_error(&result); + } + } else { + info!("You got a pm from another user ({})", sender) + } + } + _ => {} + }, + _ => {} + } + }, + Ok(action) = actions.recv_async() => { + match action { + ClientAction::Connected => { + client.state.logged_in = false; + let result = client.login(client.state.last_credentials.clone().unwrap()).await; + if result.is_err() { + handle_error(&result); + break; + } else { + info!("Reconnected to the chat") + } + } + ClientAction::Disconnected => {} + } + }, + } + } + } else { + handle_error(&result); + } + } else { + handle_error(&result); + } + } else { + error!("You need to enter a username") + } + } "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); + let id = split.next().or(Some("")).unwrap(); + if argument == "inbox" && !id.is_empty() { + let parsed = id.parse(); + if let Ok(i) = parsed { + let result = client.delete_inbox_msg(i).await; + handle_error(&result); + } else { + println!("You need to specify inbox or pm and a message id"); + } + } else if argument == "pm" && !id.is_empty() { + if id.starts_with("*") { + let result = id.strip_prefix("*").unwrap().parse(); + if result.is_err() { + println!("You need to specify inbox or pm and a message id"); + return false; + } + let i: u8 = result.unwrap(); + if client.config.general.use_pm_history { + let auto_import = if client.config.acknowledgements.auto_import_on_delete { + client.config.general.auto_import_on_delete + } else { + let auto_import = ask_confirmation(rx, "You have the message history enabled but specified to delete a message that is not in the new message history. Do you want to import all new messages into the non native history that may not be accessible for all clients or delete the message without saving it into the history?").await; + if ask_confirmation(rx, "Do you want this answer to be saved?").await { + client.config.acknowledgements.auto_import_on_delete = true; + client.config.general.auto_import_on_delete = auto_import; + } + auto_import + }; + if auto_import { + let history = client.get_new_pm_history().await; + if let Ok(history) = history { + let i = i + (history.len() as u8) - 1; + handle_error(&client.update_new_pm_history().await); + handle_error(&client.move_from_new_pm_history(i).await); + } + } else { + handle_error(&client.delete_pm_msg(i, false).await); + } + } else { + handle_error(&client.delete_pm_msg(i, false).await); + } + } else { + let result = id.parse(); + if result.is_err() { + println!("You need to specify inbox or pm and a message id"); + return false; + } + let i: u8 = result.unwrap(); + handle_error(&client.move_from_new_pm_history(i).await); + }; } else { println!("You need to specify inbox or pm and a message id"); } @@ -568,6 +737,7 @@ async fn handle_line(client: &mut Client, rx: &Receiver, line: String) - println!("pay - Pay someone Steam Coins"); println!("inbox - View your inbox"); println!("pm [destination] [message]- View your private messages or send one"); + println!("chat - Chat with someone"); println!("delete - Delete a message from your inbox or PMs"); println!("motd - View the current server motd"); println!("logout - Logout of your current account"); @@ -584,8 +754,8 @@ async fn handle_line(client: &mut Client, rx: &Receiver, line: String) - async fn handle_message(client: &mut Client, response: ResponseMessage) { match response.clone().message { - Response::DisplayMessage { message, id, .. } => match id.as_str() { - "pay.received" => { + Response::DisplayMessage { message, msg_type } => match msg_type { + MsgType::PaymentReceived { amount: _, sender: _ } => { println!("{}", message); if client.config.general.show_balance_after_payment { let balance = client.get_balance().await; @@ -596,7 +766,7 @@ async fn handle_message(client: &mut Client, response: ResponseMessage) { } } } - "pm_inbox.received" => { + MsgType::PmReceived { sender: _, message: _ } => { println!("{}", message); } _ => { print_error(&report!(BankError::UnexpectedMessage(response))); } diff --git a/banklib/Cargo.toml b/banklib/Cargo.toml index 5a02d03..84b50de 100644 --- a/banklib/Cargo.toml +++ b/banklib/Cargo.toml @@ -22,3 +22,4 @@ url.workspace = true flume.workspace = true error-stack.workspace = true thiserror.workspace = true +chrono.workspace = true diff --git a/banklib/src/config.rs b/banklib/src/config.rs index e331324..bfbf2dc 100644 --- a/banklib/src/config.rs +++ b/banklib/src/config.rs @@ -10,6 +10,7 @@ pub struct Config { pub accounts: AccountConfig, pub server: ServerConfig, pub general: GeneralConfig, + pub acknowledgements: AcknowledgementsConfig } #[derive(Serialize, Deserialize)] @@ -33,6 +34,16 @@ pub struct GeneralConfig { pub show_inbox_count_after_login: bool, pub show_pm_inbox_count_after_login: bool, pub show_balance_after_payment: bool, + pub use_pm_history: bool, + pub use_fake_pm_inbox: bool, + pub auto_import_on_delete: bool, + pub chat_context_window: u8, +} + +#[derive(Serialize, Deserialize)] +pub struct AcknowledgementsConfig { + pub use_fake_pm_inbox: bool, + pub auto_import_on_delete: bool, } pub fn load_config() -> Config { @@ -42,12 +53,15 @@ pub fn load_config() -> Config { let json: serde_json::Result = serde_json::from_str(content.unwrap().as_str()); if json.is_ok() { return json.unwrap(); + } else { + error!("Config invalid") } } else { error!("Config invalid") } } - Config { + + let config = Config { accounts: AccountConfig { default_account: "".to_string(), last_account: "".to_string(), @@ -64,8 +78,18 @@ pub fn load_config() -> Config { show_inbox_count_after_login: true, show_pm_inbox_count_after_login: true, show_balance_after_payment: true, + use_pm_history: true, + use_fake_pm_inbox: true, + auto_import_on_delete: true, + chat_context_window: 10, }, - } + acknowledgements: AcknowledgementsConfig { + use_fake_pm_inbox: false, + auto_import_on_delete: false, + } + }; + save_config(&config); + config } pub fn save_config(config: &Config) { diff --git a/banklib/src/extended.rs b/banklib/src/extended.rs index f40d5dc..8bc3f97 100644 --- a/banklib/src/extended.rs +++ b/banklib/src/extended.rs @@ -1,8 +1,10 @@ #![cfg(feature = "extended")] +use chrono::{DateTime, Utc}; use error_stack::report; -use tracing::info; -use crate::{AuthenticatedMessage, BankClient, BankError, BankResult, Response, ResponseMessage}; +use serde::{Deserialize, Serialize}; +use tracing::{info, warn}; +use crate::{AuthenticatedMessage, BankClient, BankError, BankResult, MsgType, Response, ResponseMessage}; use crate::config::{save_config, Config}; #[derive(Clone, Eq, PartialEq)] @@ -29,10 +31,10 @@ macro_rules! check_message_id { match &message.message { Response::DisplayMessage { message: _, - id, + msg_type, .. } => { - matches!(id.as_str(), $pattern) + matches!(msg_type, $pattern) } _ => false, } @@ -41,31 +43,31 @@ macro_rules! check_message_id { }}; } -fn display_message(message: Response) -> (String, String, u32) { +fn display_message(message: Response) -> (String, MsgType) { match message { - Response::DisplayMessage { message, id, value, .. } => return (message, id, value), + Response::DisplayMessage { message, msg_type } => return (message, msg_type), _ => panic!("Unexpected message type"), } } impl Client { pub async fn logout(&mut self) -> BankResult<(), BankError> { - let (message, id, _) = display_message( + let (message, msg_type) = display_message( self.client .send_authenticated( AuthenticatedMessage::Logout, check_message_id!( - "logout.success" | "logout.fail.notloggedin" | "logout.fail.credentials" + MsgType::LogoutSuccess | MsgType::LogoutFailNotLoggedIn | MsgType::LogoutFailCredentials ), ) .await?, ); info!("{}", message); - match id.as_str() { - "logout.success" | "logout.fail.notloggedin" => { + match msg_type { + MsgType::LogoutSuccess | MsgType::LogoutFailNotLoggedIn => { self.state.logged_in = false; } - "logout.fail.credentials" => return Err(report!(BankError::AuthenticationError)), + MsgType::LogoutFailCredentials => return Err(report!(BankError::AuthenticationError)), _ => panic!("Unexpected message"), } Ok(()) @@ -76,51 +78,45 @@ impl Client { 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?, + let (message, msg_type) = display_message( + self.client.send_authenticated(AuthenticatedMessage::Authenticate, check_message_id!(MsgType::AuthSuccess { new_id: _ } | MsgType::AuthFailCredentials)).await?, ); info!("{}", message); - match id.as_str() { - "auth.success" => { + match msg_type { + MsgType::AuthSuccess { new_id } => { 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); + info!("(Client Id: {})", new_id); if self.config.general.show_motd_after_login { let motd = self.get_motd().await; if let Ok(motd) = motd { info!("--- {} ---", motd); + } else { + return Err(motd.err().unwrap()); } } 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" } - ); + info!("You have {} unread message{}", size, if size == 1 { "" } else { "s" }); + } else { + return Err(size.err().unwrap()); } } 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" } - ); + let size = self.get_real_new_pm_count().await; + if let Ok((new, unread)) = size { + let size = new + unread; + info!("You have a total of {} ({} new) unread private message{}", size, new, if size == 1 { "" } else { "s" }); + } else { + return Err(size.err().unwrap()); } } } - "auth.fail.credentials" => return Err(report!(BankError::AuthenticationError)), + MsgType::AuthFailCredentials => return Err(report!(BankError::AuthenticationError)), _ => panic!("Unexpected message"), } Ok(()) @@ -128,11 +124,11 @@ impl Client { 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)), + let (_, msg_type) = display_message(self.client.send_authenticated( + AuthenticatedMessage::Register, check_message_id!(MsgType::RegisterSuccess | MsgType::RegisterFailUsernameTaken)).await?); + match msg_type { + MsgType::RegisterSuccess => Ok(()), + MsgType::RegisterFailUsernameTaken => Err(report!(BankError::AuthenticationError)), _ => panic!("Unexpected message"), } } @@ -154,17 +150,17 @@ impl Client { } pub async fn get_inbox_size(&mut self) -> BankResult { - let (_, id, value) = display_message( + let (_, msg_type) = display_message( self.client .send_authenticated( AuthenticatedMessage::GetInbox { message_id: -1 }, - check_message_id!("message_count" | "inbox.fail.credentials"), + check_message_id!(MsgType::InboxMessageCount { message_count: _ } | MsgType::InboxFailCredentials), ) .await?, ); - match id.as_str() { - "message_count" => Ok(value as u8), - "inbox.fail.credentials" => Err(report!(BankError::AuthenticationError)), + match msg_type { + MsgType::InboxMessageCount { message_count } => Ok(message_count as u8), + MsgType::InboxFailCredentials => Err(report!(BankError::AuthenticationError)), _ => panic!("Unexpected message"), } } @@ -173,17 +169,17 @@ impl Client { 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") + Response::DisplayMessage { message: _, msg_type } => { + msg_type.eq(&MsgType::InboxFailCredentials) } _ => 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)), + Response::DisplayMessage { message: _, msg_type } => { + match msg_type { + MsgType::InboxFailCredentials => Err(report!(BankError::AuthenticationError)), _ => panic!("Unexpected message"), } } @@ -192,27 +188,27 @@ impl Client { } 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?); + let (message, msg_type) = display_message(self.client.send_authenticated( + AuthenticatedMessage::ReadInbox { message_id: message_id as i8 }, check_message_id!(MsgType::DeleteInboxSuccess | MsgType::DeleteInboxFailCredentials)).await?); info!("{}", message); - match id.as_str() { - "read_inbox.success" => Ok(()), - "read_inbox.fail.credentials" => Err(report!(BankError::AuthenticationError)), + match msg_type { + MsgType::DeleteInboxSuccess => Ok(()), + MsgType::DeleteInboxFailCredentials => Err(report!(BankError::AuthenticationError)), _ => panic!("Unexpected message"), } } pub async fn get_pm_size(&mut self) -> BankResult { - let (_, id, value) = display_message( + let (_, msg_type) = display_message( self.client .send_authenticated( AuthenticatedMessage::GetPmInbox { message_id: -1 }, - check_message_id!("pm_message_count" | "pm.fail.credentials"), + check_message_id!(MsgType::PmMessageCount { message_count: _ } | MsgType::PmFailCredentials), ) .await?); - match id.as_str() { - "pm_message_count" => Ok(value as u8), - "pm.fail.credentials" => Err(report!(BankError::AuthenticationError)), + match msg_type { + MsgType::PmMessageCount { message_count } => Ok(message_count as u8), + MsgType::PmFailCredentials => Err(report!(BankError::AuthenticationError)), _ => panic!("Unexpected message"), } } @@ -221,17 +217,17 @@ impl Client { 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") + Response::DisplayMessage { message: _, msg_type } => { + msg_type.eq(&MsgType::PmFailCredentials) } _ => 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)), + Response::DisplayMessage { message: _, msg_type } => { + match msg_type { + MsgType::PmFailCredentials => Err(report!(BankError::AuthenticationError)), _ => panic!("Unexpected message"), } } @@ -239,53 +235,232 @@ impl Client { } } - 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)), + pub async fn delete_pm_msg(&mut self, message_id: u8, silent: bool) -> BankResult<(), BankError> { + let (message, msg_type) = display_message(self.client.send_authenticated( + AuthenticatedMessage::ReadPmInbox { message_id: message_id as i8 }, check_message_id!(MsgType::DeletePmSuccess | MsgType::DeletePmFailCredentials)).await?); + if !silent { + info!("{}", message); + } + match msg_type { + MsgType::DeletePmSuccess => Ok(()), + MsgType::DeletePmFailCredentials => Err(report!(BankError::AuthenticationError)), _ => panic!("Unexpected message"), } } pub async fn get_balance(&mut self) -> BankResult { - 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)), + let (_, msg_type) = display_message(self.client.send_authenticated( + AuthenticatedMessage::GetBalance, check_message_id!(MsgType::BalanceSuccess { balance: _ } | MsgType::BalanceFailCredentials)).await?); + match msg_type { + MsgType::BalanceSuccess { balance } => Ok(balance), + MsgType::BalanceFailCredentials => 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?); + let (message, msg_type) = display_message(self.client.send_authenticated( + AuthenticatedMessage::Pay { destination, amount }, check_message_id!(MsgType::PaySuccess | MsgType::PayFailInternalError | + MsgType::PayFailNotEnoughMoney | MsgType::PayFailCredentials | MsgType::PayFailUnknownDestination | + MsgType::PayFailUnknownUsername | MsgType::PayFailNegativeAmount)).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)), + match msg_type { + MsgType::PaySuccess => Ok(()), + MsgType::PayFailNotEnoughMoney => Err(report!(BankError::NotEnoughMoney)), + MsgType::PayFailUnknownDestination => Err(report!(BankError::DestinationUnknown)), + MsgType::PayFailCredentials => Err(report!(BankError::AuthenticationError)), + MsgType::PayFailUnknownUsername => Err(report!(BankError::AuthenticationError)), + MsgType::PayFailNegativeAmount => Err(report!(BankError::InternalError)), + MsgType::PayFailInternalError => 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)), + let (server_message, msg_type) = display_message(self.client.send_authenticated( + AuthenticatedMessage::SendPm { destination: destination.clone(), message: message.clone() }, check_message_id!(MsgType::PmSendSuccess | MsgType::PMFailDestinationUnknown | MsgType::PmFailCredentials)).await?); + info!("{}", server_message); + match msg_type { + MsgType::PmSendSuccess => { + if self.config.general.use_pm_history { + let current_username = self.state.last_credentials.clone().unwrap().username; + self.add_to_pm_history(destination, MessageHistoryEntry { message, sender: current_username, time: Utc::now() }).await?; + } + Ok(()) + }, + MsgType::PMFailDestinationUnknown => Err(report!(BankError::DestinationUnknown)), + MsgType::PmFailCredentials => Err(report!(BankError::AuthenticationError)), _ => panic!("Unexpected message"), } } -} \ No newline at end of file + + pub async fn set_custom_data(&mut self, key: String, data: String) -> BankResult<(), BankError> { + let response = self.client.send_authenticated(AuthenticatedMessage::CustomDataInsert { key, data }, |msg| { + match &msg.message { + Response::CustomData { response_type, .. } => { + response_type.eq("insert") + } + _ => false + } + }).await?; + match response { + Response::CustomData { data, .. } => { + if data.eq("success") { + Ok(()) + } else { + Err(report!(BankError::InternalError)) + } + }, + _ => panic!("Unexpected message type"), + } + } + + pub async fn get_custom_data(&mut self, key: String) -> BankResult { + let response = self.client.send_authenticated(AuthenticatedMessage::CustomDataGet { key }, |msg| { + match &msg.message { + Response::CustomData { response_type, .. } => { + response_type.eq("get") + } + _ => false + } + }).await?; + match response { + Response::CustomData { data, .. } => Ok(data), + _ => panic!("Unexpected message type"), + } + } + + /*More extended*/ + + pub async fn get_pm_history(&mut self, username: String) -> BankResult, BankError> { + let data = self.get_custom_data(format!("pm_history.{}", username)).await?; + if data.is_empty() { + Ok(Vec::new()) + } else { + let history: BankResult, BankError> = serde_json::from_str(&data).map_err(|_| report!(BankError::InvalidData)); + history + } + } + + pub async fn set_pm_history(&mut self, username: String, history: Vec) -> BankResult<(), BankError> { + self.set_custom_data(format!("pm_history.{}", username), serde_json::to_string(&history).map_err(|_| report!(BankError::InvalidData))?).await + } + + pub async fn add_to_pm_history(&mut self, username: String, entry: MessageHistoryEntry) -> BankResult<(), BankError> { + let mut history = self.get_pm_history(username.clone()).await?.clone(); + history.push(entry); + self.set_pm_history(username, history).await + } + + pub async fn get_new_pms(&mut self) -> BankResult, BankError> { + let size = self.get_pm_size().await?; + let mut pms = Vec::new(); + for i in 0..size { + pms.push(self.get_pm_msg((i + 1) as i8).await?); + } + Ok(pms) + } + + pub async fn delete_new_pms(&mut self) -> BankResult<(), BankError> { + let size = self.get_pm_size().await?; + for i in 0..size { + self.delete_pm_msg(i + 1, true).await?; + } + Ok(()) + } + + pub async fn update_new_pm_history(&mut self) -> BankResult<(), BankError> { + let pms = self.get_new_pms().await?; + let mut history: Vec = self.get_new_pm_history().await?.clone(); + for (_, message) in pms { + history.push(Self::entry_from_new_pm(message)) + } + + self.set_pm_history("new".into(), history).await?; + + let result = self.delete_new_pms().await; + if result.is_err() { + warn!("Error moving new PMs from native to custom data - PMs are now possibly duplicate"); + return result; + } + info!("Successfully updated the list of unread PMs"); + Ok(()) + } + + fn entry_from_new_pm(message: String) -> MessageHistoryEntry { + let split: Vec<&str> = message.split(':').collect(); + let (sender, message) = if split.len() <= 1 { + warn!("Cannot determine sender of PM"); + ("Unknown".into(), message) + } else { + (split[0].into(), split[1].trim_start().into()) + }; + + MessageHistoryEntry { message, sender, time: Utc::now() } + } + + pub async fn get_new_pm_history(&mut self) -> BankResult, BankError> { + self.get_pm_history("new".into()).await + } + + pub async fn delete_new_pm_history(&mut self) -> BankResult<(), BankError> { + self.set_pm_history("new".into(), Vec::new()).await + } + + pub async fn move_from_new_pm_history(&mut self, id: u8) -> BankResult<(), BankError> { + let mut history = self.get_new_pm_history().await?; + let entry = history.remove(id as usize); + self.add_to_pm_history(entry.sender.clone(), entry).await?; + self.set_pm_history("new".into(), history).await + } + + pub async fn move_newest_pm_to_history(&mut self) -> BankResult { + let pms = self.get_new_pms().await?; + let entry = Self::entry_from_new_pm(pms.iter().last().unwrap().1.clone()); + self.add_to_pm_history(entry.sender.clone(), entry.clone()).await?; + let result = self.delete_pm_msg(pms.len() as u8, true).await; + if result.is_err() { + warn!("Error moving new PM to history - PMs are now possibly duplicate"); + } + Ok(entry) + } + + pub async fn move_all_new_pms(&mut self) -> BankResult<(), BankError> { + if self.config.general.use_fake_pm_inbox { + if self.config.general.use_pm_history { + let history = self.get_new_pm_history().await?; + for msg in history { + let result = self.add_to_pm_history(msg.sender.clone(), msg).await; + if result.is_err() { + warn!("Error saving pms to history - PMs are now possible duplicate"); + return result; + } + } + } + self.delete_new_pm_history().await?; + Ok(()) + } else { + if !self.config.general.use_pm_history { + /*if !ask_confirmation(rx, */warn!("You have the PM-History enabled but have disabled the history for new PMs. New PMs will now just be deleted and not saved into the history. Are you sure you want to continue?")/*.await {*/ + /* return Ok(()); + }*/ + } + self.delete_new_pms().await?; + self.delete_new_pm_history().await?; + Ok(()) + } + } + + pub async fn get_real_new_pm_count(&mut self) -> BankResult<(u8, u8), BankError> { + let i = self.get_pm_size().await?; + let i2 = self.get_new_pm_history().await?.len(); + Ok((i, i2 as u8)) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MessageHistoryEntry { + pub message: String, + pub sender: String, + pub time: DateTime, +} diff --git a/banklib/src/lib.rs b/banklib/src/lib.rs index 12cb8b4..c40cd9b 100644 --- a/banklib/src/lib.rs +++ b/banklib/src/lib.rs @@ -43,7 +43,9 @@ pub enum BankError { #[error("You don't have enough Steam Coins")] NotEnoughMoney, #[error("Unknown unexpected message received {_0:?}")] - UnexpectedMessage(ResponseMessage) + UnexpectedMessage(ResponseMessage), + #[error("Error handling Data")] + InvalidData, } pub type BankResult = error_stack::Result; @@ -191,20 +193,27 @@ pub enum AuthenticatedMessage { message: String, }, Motd, + CustomDataInsert { + #[serde(rename = "value3")] + key: String, + #[serde(rename = "value4")] + data: String, + }, + CustomDataGet { + #[serde(rename = "value3")] + key: String, + }, } #[derive(Deserialize, Clone, Debug)] -#[serde(tag = "action", rename_all = "camelCase")] +#[serde(tag = "action", rename_all = "snake_case")] pub enum Response { + #[serde(rename = "displayMessage")] 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, + #[serde(flatten)] + msg_type: MsgType, }, Inbox { #[serde(rename = "value1")] @@ -212,7 +221,6 @@ pub enum Response { #[serde(rename = "value2")] message: String, }, - #[serde(rename = "pm_inbox")] PmInbox { #[serde(rename = "value1")] message_id: u8, @@ -223,6 +231,95 @@ pub enum Response { #[serde(rename = "value1")] motd: String, }, + CustomData { + #[serde(rename = "value1")] + response_type: String, + #[serde(rename = "value2")] + data: String, + } +} + +#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(tag = "value2")] +pub enum MsgType { + #[serde(rename = "auth.success")] + AuthSuccess { + #[serde(rename = "value3")] + new_id: u32, + }, + #[serde(rename = "auth.fail.credentials")] + AuthFailCredentials, + #[serde(rename = "register.success")] + RegisterSuccess, + #[serde(rename = "register.fail.usernameTaken")] + RegisterFailUsernameTaken, + #[serde(rename = "logout.success")] + LogoutSuccess, + #[serde(rename = "logout.fail.credentials")] + LogoutFailCredentials, + #[serde(rename = "logout.fail.notloggedin")] + LogoutFailNotLoggedIn, + #[serde(rename = "pay.success")] + PaySuccess, + #[serde(rename = "pay.fail.negative_amount")] + PayFailNegativeAmount, + #[serde(rename = "pay.fail.orig_user_unknown")] + PayFailUnknownUsername, + #[serde(rename = "pay.fail.unknown_dest")] + PayFailUnknownDestination, + #[serde(rename = "pay.fail.credentials")] + PayFailCredentials, + #[serde(rename = "pay.fail.not_enough_money")] + PayFailNotEnoughMoney, + #[serde(rename = "pay.fail.unknown_error")] + PayFailInternalError, + #[serde(rename = "inbox.fail.credentials")] + InboxFailCredentials, + #[serde(rename = "pm.fail.credentials")] + PmFailCredentials, + #[serde(rename = "balance.fail.credentials")] + BalanceFailCredentials, + #[serde(rename = "balance.success")] + BalanceSuccess { + #[serde(rename = "value3")] + balance: u32, + }, + #[serde(rename = "pm_inbox.send.success")] + PmSendSuccess, + #[serde(rename = "pm_inbox.dest.unknown")] + PMFailDestinationUnknown, + #[serde(rename = "read_inbox.success")] + DeleteInboxSuccess, + #[serde(rename = "read_inbox.fail.credentials")] + DeleteInboxFailCredentials, + #[serde(rename = "read_pm.success")] + DeletePmSuccess, + #[serde(rename = "read_pm.fail.credentials")] + DeletePmFailCredentials, + #[serde(rename = "pay.received")] + PaymentReceived { + #[serde(rename = "value3")] + amount: u32, + #[serde(rename = "value4")] + sender: String, + }, + #[serde(rename = "pm_inbox.received")] + PmReceived { + #[serde(rename = "value3")] + sender: String, + #[serde(rename = "value4")] + message: String, + }, + #[serde(rename = "message_count")] + InboxMessageCount { + #[serde(rename = "value3")] + message_count: u32, + }, + #[serde(rename = "pm_message_count")] + PmMessageCount { + #[serde(rename = "value3")] + message_count: u32, + }, } #[derive(Deserialize, Clone, Debug)] @@ -237,6 +334,7 @@ fn default_i32() -> i32 { -1 } +#[allow(dead_code)] fn default_u32() -> u32 { 0 } @@ -252,10 +350,10 @@ impl ezsockets::ClientExt for Handler { 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; + Response::DisplayMessage { message: _, msg_type } => { + match msg_type { + MsgType::AuthSuccess { new_id } => { + self.id = new_id.clone() as i32; } _ => {} } diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index e69de29..0000000