diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 587f29cd..5fe6cb1f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3357,6 +3357,7 @@ dependencies = [ "rand_distr", "rayon", "regex", + "serde", "smartstring", "thiserror", "wasm-timer", @@ -4346,6 +4347,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" dependencies = [ "autocfg", + "serde", "static_assertions", "version_check", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8e2f20a9..29484e1f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -22,7 +22,7 @@ regex = "1.9.1" directories = "5.0.1" tokio = { version = "1", features = ["full"] } csv = "1.1.6" -polars = { version = "0.30.0", features = ["lazy"] } +polars = { version = "0.30.0", features = ["lazy", "serde"] } sqlx = { version = "0.7.1", features = ["runtime-tokio-native-tls", "sqlite"] } once_cell = "1.7" chrono = "0.4" diff --git a/src-tauri/src/commands/base.rs b/src-tauri/src/commands/base.rs index 3caf55a3..d3ed2e2b 100644 --- a/src-tauri/src/commands/base.rs +++ b/src-tauri/src/commands/base.rs @@ -30,6 +30,8 @@ use crate::{ PACKAGEINFO, }; +use super::auth; + #[tauri::command] pub async fn init( settings: tauri::State<'_, Arc>>, @@ -178,11 +180,14 @@ pub async fn update_settings( let arced_mutex = Arc::clone(&settings_state); let mut my_lock = arced_mutex.lock()?; + // Set Loggin Settings + my_lock.debug = settings.debug; + // Set Live Scraper Settings my_lock.live_scraper = settings.live_scraper; // Set Whisper Scraper Settings - my_lock.whisper_scraper = settings.whisper_scraper; + my_lock.notifications = settings.notifications; my_lock.save_to_file().expect("Could not save settings"); Ok(()) @@ -213,6 +218,41 @@ pub fn show_notification( ); } +#[tauri::command] +pub fn on_new_wfm_message( + message: crate::wfm_client::modules::chat::ChatMessage, + auth: tauri::State<'_, Arc>>, + settings: tauri::State<'_, Arc>>, + mh: tauri::State<'_, Arc>>, +) { + let mh = mh.lock().unwrap(); + let auth = auth.lock().unwrap().clone(); + let settings = settings.lock().unwrap().clone().notifications.on_wfm_chat_message; + + if auth.id == message.message_from { + return; + } + + let content = settings.content.replace("", &message.raw_message.unwrap_or("".to_string())); + if settings.system_notify { + mh.show_notification( + &settings.title, + &content, + Some("https://i.imgur.com/UggEVVI.jpeg"), + Some("Default"), + ); + } + + if settings.discord_notify && settings.webhook.is_some() { + crate::helper::send_message_to_discord( + settings.webhook.unwrap_or("".to_string()), + settings.title, + content, + settings.user_ids, + ); + } +} + #[tauri::command] pub fn log( component: String, diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs index bb497fe0..5bdb3bd6 100644 --- a/src-tauri/src/error.rs +++ b/src-tauri/src/error.rs @@ -13,8 +13,8 @@ pub struct ErrorApiResponse { #[serde(rename = "error")] pub error: String, - #[serde(rename = "message")] - pub message: Vec, + #[serde(rename = "messages")] + pub messages: Vec, #[serde(skip_serializing_if = "Option::is_none", rename = "raw_response")] pub raw_response: Option, @@ -37,22 +37,22 @@ pub enum ApiResult { #[derive(Debug)] pub struct AppError { - component: &'static str, + component: String, eyre_report: String, log_level: LogLevel, } impl AppError { // Custom constructor - pub fn new(component: &'static str, eyre_report: eyre::ErrReport) -> Self { + pub fn new(component: &str, eyre_report: eyre::ErrReport) -> Self { AppError { - component, + component: component.to_string(), eyre_report: format!("{:?}", eyre_report), log_level: LogLevel::Critical, } } // Custom constructor pub fn new_api( - component: &'static str, + component: &str, mut err: ErrorApiResponse, eyre_report: eyre::ErrReport, log_level: LogLevel, @@ -80,23 +80,28 @@ impl AppError { err.body = Some(payload.clone()); extra["ApiError"] = json!(err); cause = format!( - "{}The request failed with status code {} to the url: {} with the following message: {}", + "{} The request failed with status code {} to the url: {} with the following message: {}", cause, err.status_code, err.clone().url.unwrap_or("NONE".to_string()), - err.message.join(",") + err.messages.join(",") + ); + new_err.eyre_report = format!( + "{}[J]{}[J]\n\nLocation:\n {}", + cause, + extra.to_string(), + backtrace ); - new_err.eyre_report = format!("{}[J]{}[J]\n\nLocation:\n {}", cause, extra, backtrace); new_err } // Custom constructor pub fn new_with_level( - component: &'static str, + component: &str, eyre_report: eyre::ErrReport, log_level: LogLevel, ) -> Self { AppError { - component, + component: component.to_string(), eyre_report: format!("{:?}", eyre_report), log_level, } @@ -121,7 +126,11 @@ impl AppError { } } Err(err) => { - json["error"] = json!(err.to_string()); + json["ParsingError"] = json!({ + "message": "Failed to parse the JSON in the error", + "error": err.to_string(), + "raw": json_str, + }); } } } @@ -209,7 +218,13 @@ pub fn create_log_file(file: String, e: &AppError) { crate::logger::dolog( log_level, component.as_str(), - format!("Location: {:?}, {:?}, Extra: <{:?}>", backtrace, cause, extra.to_string()).as_str(), + format!( + "Location: {:?}, {:?}, Extra: <{}>", + backtrace, + cause, + extra.to_string() + ) + .as_str(), true, Some(file.as_str()), ); diff --git a/src-tauri/src/helper.rs b/src-tauri/src/helper.rs index 7a9c8423..0d7052f3 100644 --- a/src-tauri/src/helper.rs +++ b/src-tauri/src/helper.rs @@ -61,7 +61,6 @@ pub fn send_message_to_window(event: &str, data: Option) { } } - pub async fn get_app_info() -> Result { let packageinfo = PACKAGEINFO .lock() @@ -115,11 +114,12 @@ pub async fn get_app_info() -> Result { pub fn emit_progress(id: &str, i18n_key: &str, values: Option, is_completed: bool) { send_message_to_window( "Client:Update:Progress", - Some(json!({ "id": id, "i18n_key": i18n_key,"values": values, "isCompleted": is_completed})), + Some( + json!({ "id": id, "i18n_key": i18n_key,"values": values, "isCompleted": is_completed}), + ), ); } - pub fn emit_update(update_type: &str, operation: &str, data: Option) { send_message_to_window( "Client:Update", @@ -370,41 +370,30 @@ pub fn get_column_values( column: &str, col_type: ColumnType, ) -> Result { + let error = format!( + "Column: {:?} ColumnType: {:?} Error: [] [J]{}[J]", + column, + col_type, + serde_json::to_value(&df).unwrap().to_string() + ); + let df: DataFrame = match filter { Some(filter) => df.lazy().filter(filter).collect().map_err(|e| { - AppError::new( - "Helper", - eyre!(format!( - "Column: {:?} ColumnType: {:?} Error: {:?}", - column, col_type, e - )), - ) + AppError::new("Helper", eyre!(error.replace("[]", e.to_string().as_str()))) })?, None => df, }; - let column_series = df.column(column).map_err(|e| { - AppError::new( - "Helper", - eyre!(format!( - "Column: {:?} ColumnType: {:?} Error: {:?}", - column, col_type, e - )), - ) - })?; + let column_series = df + .column(column) + .map_err(|e| AppError::new("Helper", eyre!(error.replace("[]", e.to_string().as_str()))))?; match col_type { ColumnType::Bool => { let values: Vec = column_series .bool() .map_err(|e| { - AppError::new( - "Helper", - eyre!(format!( - "Column: {:?} ColumnType: {:?} Error: {:?}", - column, col_type, e - )), - ) + AppError::new("Helper", eyre!(error.replace("[]", e.to_string().as_str()))) })? .into_iter() .filter_map(|opt_val| opt_val) @@ -416,13 +405,7 @@ pub fn get_column_values( let values: Vec = column_series .f64() .map_err(|e| { - AppError::new( - "Helper", - eyre!(format!( - "Column: {:?} ColumnType: {:?} Error: {:?}", - column, col_type, e - )), - ) + AppError::new("Helper", eyre!(error.replace("[]", e.to_string().as_str()))) })? .into_iter() .filter_map(|opt_val| opt_val) @@ -434,13 +417,7 @@ pub fn get_column_values( let values: Vec = column_series .i64() .map_err(|e| { - AppError::new( - "Helper", - eyre!(format!( - "Column: {:?} ColumnType: {:?} Error: {:?}", - column, col_type, e - )), - ) + AppError::new("Helper", eyre!(error.replace("[]", e.to_string().as_str()))) })? .into_iter() .filter_map(|opt_val| opt_val) @@ -451,13 +428,7 @@ pub fn get_column_values( let values: Vec = column_series .i32() .map_err(|e| { - AppError::new( - "Helper", - eyre!(format!( - "Column: {:?} ColumnType: {:?} Error: {:?}", - column, col_type, e - )), - ) + AppError::new("Helper", eyre!(error.replace("[]", e.to_string().as_str()))) })? .into_iter() .filter_map(|opt_val| opt_val) @@ -468,13 +439,7 @@ pub fn get_column_values( let values = column_series .utf8() .map_err(|e| { - AppError::new( - "Helper", - eyre!(format!( - "Column: {:?} ColumnType: {:?} Error: {:?}", - column, col_type, e - )), - ) + AppError::new("Helper", eyre!(error.replace("[]", e.to_string().as_str()))) })? .into_iter() .filter_map(|opt_name| opt_name.map(String::from)) @@ -770,3 +735,32 @@ pub fn get_warframe_language() -> WarframeLanguage { // Default to English in case of any error WarframeLanguage::English } + +pub fn validate_json(json: &Value, required: &Value, path: &str) -> (Value, Vec) { + let mut modified_json = json.clone(); + let mut missing_properties = Vec::new(); + + if let Some(required_obj) = required.as_object() { + for (key, value) in required_obj { + let full_path = if path.is_empty() { + key.clone() + } else { + format!("{}.{}", path, key) + }; + + if !json.as_object().unwrap().contains_key(key) { + missing_properties.push(full_path.clone()); + modified_json[key] = required_obj[key].clone(); + } else if value.is_object() { + let sub_json = json.get(key).unwrap(); + let (modified_sub_json, sub_missing) = validate_json(sub_json, value, &full_path); + if !sub_missing.is_empty() { + modified_json[key] = modified_sub_json; + missing_properties.extend(sub_missing); + } + } + } + } + + (modified_json, missing_properties) +} diff --git a/src-tauri/src/live_scraper/client.rs b/src-tauri/src/live_scraper/client.rs index 2f681381..af8b9687 100644 --- a/src-tauri/src/live_scraper/client.rs +++ b/src-tauri/src/live_scraper/client.rs @@ -61,20 +61,23 @@ impl LiveScraperClient { let backtrace = error.backtrace(); let log_level = error.log_level(); let extra = error.extra_data(); - if log_level == LogLevel::Critical { + if log_level == LogLevel::Critical || log_level == LogLevel::Error { self.is_running.store(false, Ordering::SeqCst); crate::logger::dolog( log_level.clone(), component.as_str(), - format!("Error: {:?}, {:?}, {:?}", backtrace, cause, extra).as_str(), + format!("{}, {}, {}", backtrace, cause, extra.to_string()).as_str(), true, Some(self.log_file.as_str()), ); helper::send_message_to_window("LiveScraper:Error", Some(error.to_json())); } else { - logger::info_con( - "LiveScraper", - format!("Error: {:?}, {:?}", backtrace, cause).as_str(), + crate::logger::dolog( + log_level.clone(), + component.as_str(), + format!("{}, {}, {}", backtrace, cause, extra.to_string()).as_str(), + true, + Some(self.log_file.as_str()), ); } } @@ -82,7 +85,7 @@ impl LiveScraperClient { self.is_running.store(false, Ordering::SeqCst); } - pub fn is_running(&self) -> bool { + pub fn is_running(&self) -> bool { self.is_running.load(Ordering::SeqCst) } @@ -143,9 +146,12 @@ impl LiveScraperClient { } pub fn send_message(&self, i18n_key: &str, data: Option) { - helper::send_message_to_window("LiveScraper:UpdateMessage", Some(json!({ - "i18n_key": i18n_key, - "values": data - }))); + helper::send_message_to_window( + "LiveScraper:UpdateMessage", + Some(json!({ + "i18n_key": i18n_key, + "values": data + })), + ); } -} \ No newline at end of file +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 71260d9a..85289bb4 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -61,7 +61,10 @@ async fn setup_async(app: &mut App) -> Result<(), AppError> { app.manage(auth_arc.clone()); // create and manage Warframe Market API client state - let wfm_client = Arc::new(Mutex::new(wfm_client::client::WFMClient::new(Arc::clone(&auth_arc), Arc::clone(&settings_arc)))); + let wfm_client = Arc::new(Mutex::new(wfm_client::client::WFMClient::new( + Arc::clone(&auth_arc), + Arc::clone(&settings_arc), + ))); app.manage(wfm_client.clone()); // create and manage Cache state @@ -167,6 +170,7 @@ fn main() { commands::base::open_logs_folder, commands::base::export_logs, commands::base::show_notification, + commands::base::on_new_wfm_message, commands::auth::login, commands::auth::logout, commands::base::log, diff --git a/src-tauri/src/price_scraper.rs b/src-tauri/src/price_scraper.rs index 237a8604..bdd50460 100644 --- a/src-tauri/src/price_scraper.rs +++ b/src-tauri/src/price_scraper.rs @@ -97,7 +97,7 @@ impl PriceScraper { let mut error_def = ErrorApiResponse { status_code: 500, error: "UnknownError".to_string(), - message: vec![], + messages: vec![], raw_response: None, body: None, url: Some(url.clone()), @@ -106,7 +106,7 @@ impl PriceScraper { if let Err(e) = response { - error_def.message.push(e.to_string()); + error_def.messages.push(e.to_string()); return Err(AppError::new_api( "PriceScraper", error_def, @@ -128,7 +128,7 @@ impl PriceScraper { // Convert the response to a Value object let response: Value = serde_json::from_str(content.as_str()).map_err(|e| { - error_def.message.push(e.to_string()); + error_def.messages.push(e.to_string()); error_def.error = "ParseError".to_string(); AppError::new_api( "PriceScraper", @@ -205,7 +205,7 @@ impl PriceScraper { // Get the price data for the day for all items match self.get_price_by_day(auth.platform.as_str(), &day).await { - Ok(ApiResult::Success(items, headers)) => { + Ok(ApiResult::Success(items, _headers)) => { found_data += 1; logger::info_con( "PriceScraper", diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index cc1af3e6..b585ebbd 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::fs::File; -use std::io::{self, Read, Write}; +use std::io::{Read, Write}; use std::path::PathBuf; use crate::enums::{OrderMode, StockMode}; @@ -12,9 +12,10 @@ use eyre::eyre; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SettingsState { // Debug Mode - pub debug: bool, + pub debug: Vec, + pub dev_mode: bool, pub live_scraper: LiveScraperSettings, - pub whisper_scraper: WhisperSettings, + pub notifications: Notifications, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct LiveScraperSettings { @@ -49,9 +50,10 @@ pub struct StockRivenSettings { pub range_threshold: i64, } -#[derive(Clone, Debug,Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Notification { - pub enable: bool, + pub discord_notify: bool, + pub system_notify: bool, pub content: String, pub title: String, // Use For Discord @@ -60,19 +62,16 @@ pub struct Notification { } #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct NewConversion { - pub discord: Notification, - pub system: Notification, -} -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct WhisperSettings { - pub on_new_conversation: NewConversion, +pub struct Notifications { + pub on_new_conversation: Notification, + pub on_wfm_chat_message: Notification, } // Allow us to run AuthState::default() impl Default for SettingsState { fn default() -> Self { Self { - debug: false, + debug: vec!["*".to_string()], + dev_mode: false, live_scraper: LiveScraperSettings { stock_mode: StockMode::All, webhook: "".to_string(), @@ -87,28 +86,28 @@ impl Default for SettingsState { strict_whitelist: false, report_to_wfm: true, auto_trade: true, - order_mode: OrderMode::Both + order_mode: OrderMode::Both, }, stock_riven: StockRivenSettings { range_threshold: 25, }, }, - whisper_scraper: WhisperSettings { - on_new_conversation: NewConversion { - discord: Notification { - enable: true, - content: "From: ".to_string(), - title: "New Conversation".to_string(), - webhook: Some("".to_string()), - user_ids: Some(vec![]), - }, - system: Notification { - enable: true, - content: "From: ".to_string(), - title: "You have a new in-game conversation!".to_string(), - webhook: None, - user_ids: None, - }, + notifications: Notifications { + on_new_conversation: Notification { + discord_notify: false, + system_notify: true, + content: "From: ".to_string(), + title: "New Conversation".to_string(), + webhook: Some("".to_string()), + user_ids: Some(vec![]), + }, + on_wfm_chat_message: Notification { + discord_notify: false, + system_notify: true, + content: "From: ".to_string(), + title: "New WFM Message".to_string(), + webhook: Some("".to_string()), + user_ids: Some(vec![]), }, }, } @@ -157,265 +156,29 @@ impl SettingsState { } fn validate_json(json_str: &str) -> Result<(Self, bool), AppError> { - let mut is_valid = true; // Parse the JSON string into a Value object - let mut json_value: Value = serde_json::from_str(json_str) + let json_value: Value = serde_json::from_str(json_str) .map_err(|e| AppError::new("Settings", eyre!(e.to_string())))?; - // Create a default SettingsState object - let default_settings = SettingsState::default(); - - // Check for debug mode - if json_value.get("debug").is_none() { - json_value["debug"] = - Value::from(default_settings.debug); - is_valid = false; - } - - // Check for nested properties within 'live_scraper' - if let Some(live_scraper) = json_value.get_mut("live_scraper") { - if live_scraper.get("webhook").is_none() { - live_scraper["webhook"] = Value::from(default_settings.live_scraper.webhook); - is_valid = false; - } - if live_scraper.get("stock_mode").is_none() { - live_scraper["stock_mode"] = Value::from(default_settings.live_scraper.stock_mode.as_str()); - is_valid = false; - } - - // Check for nested properties within 'stock_item' - if let Some(stock_item) = live_scraper.get_mut("stock_item") { - if stock_item.get("volume_threshold").is_none() { - stock_item["volume_threshold"] = - Value::from(default_settings.live_scraper.stock_item.volume_threshold); - is_valid = false; - } - if stock_item.get("range_threshold").is_none() { - stock_item["range_threshold"] = - Value::from(default_settings.live_scraper.stock_item.range_threshold); - is_valid = false; - } - if stock_item.get("avg_price_cap").is_none() { - stock_item["avg_price_cap"] = - Value::from(default_settings.live_scraper.stock_item.avg_price_cap); - is_valid = false; - } - if stock_item.get("max_total_price_cap").is_none() { - stock_item["max_total_price_cap"] = - Value::from(default_settings.live_scraper.stock_item.max_total_price_cap); - is_valid = false; - } - if stock_item.get("price_shift_threshold").is_none() { - stock_item["price_shift_threshold"] = Value::from( - default_settings - .live_scraper - .stock_item - .price_shift_threshold, - ); - is_valid = false; - } - if stock_item.get("blacklist").is_none() { - stock_item["blacklist"] = - Value::from(default_settings.live_scraper.stock_item.blacklist); - is_valid = false; - } - if stock_item.get("report_to_wfm").is_none() { - stock_item["report_to_wfm"] = - Value::from(default_settings.live_scraper.stock_item.report_to_wfm); - is_valid = false; - } - if stock_item.get("auto_trade").is_none() { - stock_item["auto_trade"] = - Value::from(default_settings.live_scraper.stock_item.auto_trade); - is_valid = false; - } - if stock_item.get("whitelist").is_none() { - stock_item["whitelist"] = - Value::from(default_settings.live_scraper.stock_item.whitelist); - is_valid = false; - } - if stock_item.get("strict_whitelist").is_none() { - stock_item["strict_whitelist"] = - Value::from(default_settings.live_scraper.stock_item.strict_whitelist); - is_valid = false; - } - if stock_item.get("order_mode").is_none() { - stock_item["order_mode"] = Value::from(default_settings.live_scraper.stock_item.order_mode.as_str()); - is_valid = false; - } - } else { - // If 'stock_item' itself doesn't exist, add it - live_scraper["stock_item"] = - serde_json::to_value(default_settings.live_scraper.stock_item) - .map_err(|e| AppError::new("Settings", eyre!(e.to_string())))?; - logger::info_con( - "Settings", - "Added 'live_scraper stock_item' to settings.json", - ); - is_valid = false; - } - - // Check for nested properties within 'stock_riven' - if let Some(stock_riven) = live_scraper.get_mut("stock_riven") { - if stock_riven.get("range_threshold").is_none() { - stock_riven["range_threshold"] = - Value::from(default_settings.live_scraper.stock_riven.range_threshold); - is_valid = false; - } - } else { - // If 'stock_riven' itself doesn't exist, add it - live_scraper["stock_riven"] = - serde_json::to_value(default_settings.live_scraper.stock_riven) - .map_err(|e| AppError::new("Settings", eyre!(e.to_string())))?; - logger::info_con( - "Settings", - "Added 'live_scraper stock_riven' to settings.json", - ); - is_valid = false; - } - } else { - // If 'live_scraper' itself doesn't exist, add it - json_value["live_scraper"] = serde_json::to_value(default_settings.live_scraper) - .map_err(|e| AppError::new("Settings", eyre!(e.to_string())))?; - is_valid = false; - } + // Required properties for the settings.json file + let required_json = serde_json::to_value(SettingsState::default()) + .map_err(|e| AppError::new("Settings", eyre!(e.to_string())))?; - // Check for nested properties within 'whisper_scraper' - if let Some(whisper_scraper) = json_value.get_mut("whisper_scraper") { - // Check for nested properties within 'on_new_conversation' - if let Some(on_new_conversation) = whisper_scraper.get_mut("on_new_conversation") { - // Check for nested properties within 'on_new_conversation' - if let Some(system) = on_new_conversation.get_mut("system") { - if system.get("enable").is_none() { - system["enable"] = Value::from( - default_settings - .whisper_scraper - .on_new_conversation - .system - .enable, - ); - is_valid = false; - } - if system.get("title").is_none() { - system["title"] = Value::from( - default_settings - .whisper_scraper - .on_new_conversation - .system - .title, - ); - is_valid = false; - } - if system.get("content").is_none() { - system["content"] = Value::from( - default_settings - .whisper_scraper - .on_new_conversation - .system - .content, - ); - is_valid = false; - } - } else { - // If 'stock_item' itself doesn't exist, add it - on_new_conversation["system"] = serde_json::to_value( - default_settings.whisper_scraper.on_new_conversation.system, - ) - .map_err(|e| AppError::new("Settings", eyre!(e.to_string())))?; - logger::info_con( - "Settings", - "Added 'on_new_conversation system' to settings.json", - ); - is_valid = false; - } + // Validate the JSON object against the required properties + let (validated_json, missing_properties) = + helper::validate_json(&json_value, &required_json, ""); - // Check for nested properties within 'on_new_conversation' - if let Some(discord) = on_new_conversation.get_mut("discord") { - if discord.get("enable").is_none() { - discord["enable"] = Value::from( - default_settings - .whisper_scraper - .on_new_conversation - .discord - .enable, - ); - is_valid = false; - } - if discord.get("title").is_none() { - discord["title"] = Value::from( - default_settings - .whisper_scraper - .on_new_conversation - .discord - .title, - ); - is_valid = false; - } - if discord.get("content").is_none() { - discord["content"] = Value::from( - default_settings - .whisper_scraper - .on_new_conversation - .discord - .content, - ); - is_valid = false; - } - if discord.get("webhook").is_none() { - discord["webhook"] = Value::from( - default_settings - .whisper_scraper - .on_new_conversation - .discord - .webhook, - ); - is_valid = false; - } - if discord.get("user_ids").is_none() { - discord["user_ids"] = Value::from( - default_settings - .whisper_scraper - .on_new_conversation - .discord - .user_ids, - ); - is_valid = false; - } - } else { - // If 'stock_item' itself doesn't exist, add it - on_new_conversation["discord"] = serde_json::to_value( - default_settings.whisper_scraper.on_new_conversation.discord, - ) - .map_err(|e| AppError::new("Settings", eyre!(e.to_string())))?; - logger::info_con( - "Settings", - "Added 'on_new_conversation discord' to settings.json", - ); - is_valid = false; - } - } else { - // If 'stock_item' itself doesn't exist, add it - whisper_scraper["on_new_conversation"] = - serde_json::to_value(default_settings.whisper_scraper.on_new_conversation) - .map_err(|e| AppError::new("Settings", eyre!(e.to_string())))?; - logger::info_con( - "Settings", - "Added 'whisper_scraper on_new_conversation' to settings.json", - ); - is_valid = false; + // Check for missing properties + if !missing_properties.is_empty() { + for property in missing_properties.clone() { + logger::warning_con("Settings", &format!("Missing property: {}", property)); } - } else { - // If 'live_scraper' itself doesn't exist, add it - json_value["whisper_scraper"] = serde_json::to_value(default_settings.whisper_scraper) - .map_err(|e| AppError::new("Settings", eyre!(e.to_string())))?; - logger::info_con("Settings", "Added 'whisper_scraper' to settings.json"); - is_valid = false; } // Deserialize the updated JSON object into a SettingsState struct - let deserialized: SettingsState = serde_json::from_value(json_value) + let deserialized: SettingsState = serde_json::from_value(validated_json) .map_err(|e| AppError::new("Settings", eyre!(e.to_string())))?; - Ok((deserialized, is_valid)) + Ok((deserialized, missing_properties.is_empty())) } } diff --git a/src-tauri/src/wf_ee_log_parser/client.rs b/src-tauri/src/wf_ee_log_parser/client.rs index e15f54f1..eaaaa4bb 100644 --- a/src-tauri/src/wf_ee_log_parser/client.rs +++ b/src-tauri/src/wf_ee_log_parser/client.rs @@ -1,5 +1,4 @@ use crate::cache::client::CacheClient; -use crate::database::client::DBClient; use crate::error::AppError; use crate::handler::MonitorHandler; use crate::settings::SettingsState; @@ -61,20 +60,8 @@ impl EELogParser { } } - pub fn debug(&self, component: &str, msg: &str, file: Option) { - let settings = self.settings.lock().unwrap().clone(); - if !settings.debug { - return; - } - if file.is_none() { - logger::debug(format!("{}:{}", self.component, component).as_str(), msg, true, None); - return; - } - logger::debug(format!("{}:{}", self.component, component).as_str(), msg, true, Some(&self.log_file)); - } - pub fn start_loop(&mut self) { - logger::info_con(self.component, "Starting EE Log Parser"); + logger::info_con(self.component.as_str(), "Starting EE Log Parser"); let is_running = Arc::clone(&self.is_running); let scraper = self.clone(); @@ -88,7 +75,6 @@ impl EELogParser { } Err(_) => {} } - thread::sleep(Duration::from_secs(1)); } }); @@ -97,7 +83,7 @@ impl EELogParser { } pub fn stop_loop(&self) { - logger::info_con(self.component, "Stopping Whisper Listener"); + logger::info_con(self.component.as_str(), "Stopping Whisper Listener"); self.is_running.store(false, Ordering::SeqCst); } @@ -126,7 +112,10 @@ impl EELogParser { } Err(err) => { helper::send_message_to_window("EELogParser", Some(json!({ "error": "err" }))); - Err(AppError::new(self.component, eyre::eyre!(err.to_string())))? + Err(AppError::new( + self.component.as_str(), + eyre::eyre!(err.to_string()), + ))? } } Ok(()) diff --git a/src-tauri/src/wf_ee_log_parser/events/on_new_conversation.rs b/src-tauri/src/wf_ee_log_parser/events/on_new_conversation.rs index 0f1c0ffd..613544d4 100644 --- a/src-tauri/src/wf_ee_log_parser/events/on_new_conversation.rs +++ b/src-tauri/src/wf_ee_log_parser/events/on_new_conversation.rs @@ -3,9 +3,8 @@ use std::{ sync::{Arc, Mutex}, }; -use crate::{error::AppError, helper, settings::SettingsState, logger, handler::MonitorHandler}; +use crate::{error::AppError, handler::MonitorHandler, settings::SettingsState}; use eyre::eyre; -use serde_json::json; enum Events { Conversation, @@ -20,28 +19,31 @@ impl Events { } } -#[derive(Clone,Debug)] +#[derive(Clone, Debug)] pub struct OnNewConversationEvent { - wf_ee_path: PathBuf, settings: Arc>, helper: Arc>, } impl OnNewConversationEvent { - pub fn new(settings: Arc>,helper: Arc>, wf_ee_path: PathBuf) -> Self { - Self { - settings, - helper, - wf_ee_path, - } + pub fn new( + settings: Arc>, + helper: Arc>, + _: PathBuf, + ) -> Self { + Self { settings, helper } } - pub fn check(&self, _: usize, input: &str) -> Result<(bool), AppError> { - let settings = self.settings.lock()?.clone().whisper_scraper.on_new_conversation; + pub fn check(&self, _: usize, input: &str) -> Result { + let settings = self + .settings + .lock()? + .clone() + .notifications + .on_new_conversation; let helper = self.helper.lock()?; - - if !settings.discord.enable && !settings.system.enable { + if !settings.system_notify && !settings.discord_notify { return Ok(false); } let (found, captures) = crate::wf_ee_log_parser::events::helper::match_pattern( @@ -50,44 +52,26 @@ impl OnNewConversationEvent { ) .map_err(|e| AppError::new("OnNewConversationEvent", eyre!(e)))?; if found { - self.client.debug( - "OnNewConversationEvent", - format!( - "Found a new conversation: {}", - input - ) - .as_str(), - None, - ); let username = captures.get(0).unwrap().clone().unwrap(); - + let content = settings.content.replace("", username.as_str()); // If system notification is enabled, show it - if settings.system.enable { - self.client.debug( - "OnNewConversationEvent", - format!( - "Showing system notification for: {}", - username - ) - .as_str(), - None, - ); + if settings.system_notify { helper.show_notification( - settings.system.title.as_str(), - &settings.system.content.replace("", username.as_str()), + settings.title.as_str(), + &content, Some("assets/icons/icon.png"), Some("Default"), ); } // If discord webhook is enabled, send it - if settings.discord.enable { + if settings.discord_notify && settings.webhook.is_some() { crate::helper::send_message_to_discord( - settings.discord.webhook.unwrap_or("".to_string()), - settings.discord.title, - settings.discord.content.replace("", username.as_str()), - settings.discord.user_ids.clone(), + settings.webhook.unwrap_or("".to_string()), + settings.title, + content, + settings.user_ids.clone(), ); - } + } } Ok(found) } diff --git a/src-tauri/src/wf_ee_log_parser/events/on_new_trading.rs b/src-tauri/src/wf_ee_log_parser/events/on_new_trading.rs index 0e524340..710e80ff 100644 --- a/src-tauri/src/wf_ee_log_parser/events/on_new_trading.rs +++ b/src-tauri/src/wf_ee_log_parser/events/on_new_trading.rs @@ -101,15 +101,11 @@ impl OnTradingEvent { } } pub fn check(&mut self, _index: usize, input: &str) -> Result { - // let file_path = "tradings.json"; - // let settings = self.settings.lock()?.clone().whisper_scraper; - while self.getting_trade_message_multiline { if input.contains("[Info]") || input.contains("[Error]") || input.contains("[Warning]") { self.getting_trade_message_multiline = false; self.trade_logs_finished()?; - self.client.debug("OnTradingEvent", "Trade finished", None); self.waiting_for_trade_message_confirmation = true; } else { self.received_trade_log_message(input); @@ -121,12 +117,10 @@ impl OnTradingEvent { if input.contains("[Info]: Dialog.lua: Dialog::CreateOkCancel(description=") && self.is_beginninig_of_tradelog(input)? { - self.client.debug("OnTradingEvent", "New trade detected", None); self.start_trade_log(input); if input .contains(", leftItem=/Menu/Confirm_Item_Ok, rightItem=/Menu/Confirm_Item_Cancel)") { - self.client.debug("OnTradingEvent", "Waiting for trade confirmation", None); self.waiting_for_trade_message_confirmation = true; } else { self.getting_trade_message_multiline = true; @@ -137,9 +131,9 @@ impl OnTradingEvent { else if self.waiting_for_trade_message_confirmation && input.contains("[Info]: Dialog.lua: Dialog::CreateOk(description=") { - if self.is_trade_confirmation(input)? { + if self.is_trade_confirmation(input)? { self.trade_accepted()?; - } else if self.is_trade_failed(input)? { + } else if self.is_trade_failed(input)? { self.trade_failed(); } return Ok(true); @@ -289,7 +283,7 @@ impl OnTradingEvent { } } else { trade_struct.trade_type = TradeClassification::Sale; - } + } Ok(()) } @@ -385,13 +379,11 @@ impl OnTradingEvent { } } - self.client.debug("OnTradingEvent", format!("Trade accepted from {}", trade.user_name).as_str(), None); self.reset_trade(); Ok(()) } fn trade_failed(&mut self) { - self.client.debug("OnTradingEvent", "Trade failed", None); self.reset_trade(); } diff --git a/src-tauri/src/wfm_client/client.rs b/src-tauri/src/wfm_client/client.rs index 6ea5087f..809896b1 100644 --- a/src-tauri/src/wfm_client/client.rs +++ b/src-tauri/src/wfm_client/client.rs @@ -38,7 +38,10 @@ pub struct WFMClient { } impl WFMClient { - pub fn new(auth: Arc>, settings: Arc>) -> Self { + pub fn new( + auth: Arc>, + settings: Arc>, + ) -> Self { WFMClient { endpoint: "https://api.warframe.market/v1/".to_string(), component: "WarframeMarket".to_string(), @@ -52,25 +55,42 @@ impl WFMClient { } } - pub fn debug(&self, component: &str, msg: &str, file: Option) { + pub fn debug(&self, id: &str, component: &str, msg: &str, file: Option) { let settings = self.settings.lock().unwrap().clone(); - if !settings.debug { + if !settings.debug.contains(&"*".to_owned()) && !settings.debug.contains(&id.to_owned()) { return; } + if file.is_none() { - logger::debug(format!("{}:{}", self.component, component).as_str(), msg, true, None); + logger::debug( + format!("{}:{}", self.component, component).as_str(), + msg, + true, + None, + ); return; - } - logger::debug(format!("{}:{}", self.component, component).as_str(), msg, true, Some(&self.log_file)); + } + logger::debug( + format!("{}:{}", self.component, component).as_str(), + msg, + true, + Some(&self.log_file), + ); } - pub fn create_api_error(&self, component: &str, err: ErrorApiResponse, eyre_report: eyre::ErrReport) { - return AppError::new_api( - format!("{}:{}",self.component, component).as_str(), + pub fn create_api_error( + &self, + component: &str, + err: ErrorApiResponse, + eyre_report: eyre::ErrReport, + level: LogLevel, + ) -> AppError { + return AppError::new_api( + format!("{}:{}", self.component, component).as_str(), err, eyre_report, - LogLevel::Error, - ) + level, + ); } async fn send_request( @@ -116,7 +136,7 @@ impl WFMClient { let mut error_def = ErrorApiResponse { status_code: 500, error: "UnknownError".to_string(), - message: vec![], + messages: vec![], raw_response: None, body: body.clone(), url: Some(new_url.clone()), @@ -124,9 +144,9 @@ impl WFMClient { }; if let Err(e) = response { - error_def.message.push(e.to_string()); + error_def.messages.push(e.to_string()); return Err(AppError::new_api( - self.component.as_str(), + "WarframeMarket", error_def, eyre!(format!("There was an error sending the request: {}", e)), LogLevel::Critical, @@ -142,27 +162,27 @@ impl WFMClient { // Convert the response to a Value object let response: Value = serde_json::from_str(content.as_str()).map_err(|e| { - error_def.message.push(e.to_string()); + error_def.messages.push(e.to_string()); error_def.error = "RequestError".to_string(); AppError::new_api( self.component.as_str(), error_def.clone(), - eyre!(""), + eyre!(format!("Could not parse response: {}, {:?}", content, e)), LogLevel::Critical, ) })?; // Check if the response is an error - if response.get("error").is_some() { + if response.get("error").is_some() { error_def.error = "ApiError".to_string(); // Loop through the error object and add each message to the error_def let errors: HashMap = serde_json::from_value(response["error"].clone()) .map_err(|e| { - error_def.message.push(e.to_string()); + error_def.messages.push(e.to_string()); AppError::new_api( self.component.as_str(), error_def.clone(), - eyre!(""), + eyre!(format!("Could not parse error messages: {}", e)), LogLevel::Critical, ) })?; @@ -179,10 +199,10 @@ impl WFMClient { ) })?; error_def - .message + .messages .push(format!("{}: {}", key, messages.join(", "))); } else { - error_def.message.push(format!("{}: {:?}", key, value)); + error_def.messages.push(format!("{}: {:?}", key, value)); } } return Ok(ApiResult::Error(error_def, headers)); @@ -198,12 +218,12 @@ impl WFMClient { match serde_json::from_value(data.clone()) { Ok(payload) => Ok(ApiResult::Success(payload, headers)), Err(e) => { - error_def.message.push(e.to_string()); + error_def.messages.push(e.to_string()); error_def.error = "ParseError".to_string(); return Err(AppError::new_api( self.component.as_str(), error_def, - eyre!(""), + eyre!(format!("Could not parse payload: {}", e)), LogLevel::Critical, )); } @@ -257,20 +277,35 @@ impl WFMClient { } // Add an "add" method to WFMWFMClient pub fn auth(&self) -> AuthModule { - AuthModule { client: self } + AuthModule { + client: self, + debug_id: "wfm_client_auth".to_string(), + } } pub fn orders(&self) -> OrderModule { - OrderModule { client: self } + OrderModule { + client: self, + debug_id: "wfm_client_order".to_string(), + } } pub fn items(&self) -> ItemModule { - ItemModule { client: self } + ItemModule { + client: self, + debug_id: "wfm_client_item".to_string(), + } } pub fn auction(&self) -> AuctionModule { - AuctionModule { client: self } + AuctionModule { + client: self, + debug_id: "wfm_client_auction".to_string(), + } } pub fn chat(&self) -> ChatModule { - ChatModule { client: self } + ChatModule { + client: self, + debug_id: "wfm_client_chat".to_string(), + } } } diff --git a/src-tauri/src/wfm_client/modules/auction.rs b/src-tauri/src/wfm_client/modules/auction.rs index 5da05931..5134d676 100644 --- a/src-tauri/src/wfm_client/modules/auction.rs +++ b/src-tauri/src/wfm_client/modules/auction.rs @@ -1,28 +1,32 @@ use std::sync::{Arc, Mutex}; use eyre::eyre; -use serde_json::{json, Value}; +use serde_json::json; use crate::{ - enums::LogLevel, - error::{self, ApiResult, AppError}, + error::{ApiResult, AppError}, helper, logger, structs::{ - Auction, AuctionItem, AuctionOwner, Item, ItemDetails, RivenAttribute, RivenAttributeInfo, - RivenTypeInfo, + Auction, AuctionItem, AuctionOwner, RivenAttribute, RivenAttributeInfo, RivenTypeInfo, }, wfm_client::client::WFMClient, }; pub struct AuctionModule<'a> { pub client: &'a WFMClient, + pub debug_id: String, } impl<'a> AuctionModule<'a> { pub async fn get_all_riven_types(&self) -> Result, AppError> { - match self.client.get::>("riven/items", Some("items")).await { + match self + .client + .get::>("riven/items", Some("items")) + .await + { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Auction:GetAllRivenTypes", format!("Found {} riven types", payload.len()).as_str(), None, @@ -34,6 +38,7 @@ impl<'a> AuctionModule<'a> { "Auction:GetAllRivenTypes", error, eyre!("There was an error getting all riven types"), + crate::enums::LogLevel::Error, )); } Err(err) => { @@ -49,6 +54,7 @@ impl<'a> AuctionModule<'a> { { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Auction:GetAllRivenAttributeTypes", format!("Found {} attributes", payload.len()).as_str(), None, @@ -60,6 +66,7 @@ impl<'a> AuctionModule<'a> { "Auction:GetAllRivenAttributeTypes", error, eyre!("There was an error getting all riven attribute types"), + crate::enums::LogLevel::Error, )); } Err(err) => { @@ -75,9 +82,14 @@ impl<'a> AuctionModule<'a> { ) -> Result>, AppError> { let url = format!("profile/{}/auctions", ingame_name); - match self.client.get::>>(&url, Some("auctions")).await { + match self + .client + .get::>>(&url, Some("auctions")) + .await + { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Auction:GetUsersAuctions", format!("Found {} auctions", payload.len()).as_str(), None, @@ -88,7 +100,11 @@ impl<'a> AuctionModule<'a> { return Err(self.client.create_api_error( "Auction:GetUsersAuctions", error, - eyre!("There was an error getting all auctions for user: {}", ingame_name), + eyre!( + "There was an error getting all auctions for user: {}", + ingame_name + ), + crate::enums::LogLevel::Error, )); } Err(err) => { @@ -103,18 +119,6 @@ impl<'a> AuctionModule<'a> { Ok(auctions) } - pub async fn get_auction_by_id( - &self, - auction_id: &str, - ) -> Result>, AppError> { - let auctions = self.get_my_auctions().await?; - - let auction = auctions - .iter() - .find(|auction| auction.id == auction_id) - .clone(); - Ok(auction.cloned()) - } pub async fn create( &self, auction_type: &str, @@ -163,10 +167,12 @@ impl<'a> AuctionModule<'a> { Ok(ApiResult::Success(payload, _headers)) => { self.emit("CREATE_OR_UPDATE", serde_json::to_value(&payload).unwrap()); self.client.debug( + &self.debug_id, "Auction:Create", format!( "Created auction for type: {} for item: {}", - auction_type, item.name.unwrap_or("None".to_string()) + auction_type, + item.name.unwrap_or("None".to_string()) ) .as_str(), None, @@ -178,6 +184,7 @@ impl<'a> AuctionModule<'a> { "Auction:Create", error, eyre!("There was an error creating the auction"), + crate::enums::LogLevel::Error, )); } Err(err) => { @@ -209,6 +216,7 @@ impl<'a> AuctionModule<'a> { Ok(ApiResult::Success(payload, _headers)) => { self.emit("CREATE_OR_UPDATE", serde_json::to_value(&payload).unwrap()); self.client.debug( + &self.debug_id, "Auction:Update", format!( "Updated auction: {} to buyout price: {}", @@ -224,6 +232,7 @@ impl<'a> AuctionModule<'a> { "Auction:Update", error, eyre!("There was an error updating the auction"), + crate::enums::LogLevel::Error, )); } Err(err) => { @@ -294,6 +303,7 @@ impl<'a> AuctionModule<'a> { { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Auction:Search", format!( "Found {} auctions using query: {}", @@ -306,10 +316,15 @@ impl<'a> AuctionModule<'a> { return Ok(payload); } Ok(ApiResult::Error(error, _headers)) => { + let log_level = match error.status_code { + 400 => crate::enums::LogLevel::Warning, + _ => crate::enums::LogLevel::Error, + }; return Err(self.client.create_api_error( "Auction:Search", error, - eyre!("There was an error searching for auctions"), + eyre!("There was an error searching for auctions."), + log_level, )); } Err(err) => { @@ -324,6 +339,7 @@ impl<'a> AuctionModule<'a> { Ok(ApiResult::Success(payload, _headers)) => { self.emit("CREATE_OR_UPDATE", serde_json::to_value(&payload).unwrap()); self.client.debug( + &self.debug_id, "Auction:Delete", format!("Deleted auction: {}", auction_id).as_str(), None, @@ -331,10 +347,20 @@ impl<'a> AuctionModule<'a> { return Ok(payload); } Ok(ApiResult::Error(error, _headers)) => { + let log_level = match error.messages.get(0) { + Some(message) + if message.contains("app.form.not_exist") + || message.contains("app.form.invalid") => + { + crate::enums::LogLevel::Warning + } + _ => crate::enums::LogLevel::Error, + }; return Err(self.client.create_api_error( "Auction:Delete", error, eyre!("There was an error deleting the auction"), + log_level, )); } Err(err) => { diff --git a/src-tauri/src/wfm_client/modules/auth.rs b/src-tauri/src/wfm_client/modules/auth.rs index 52d3ab40..c5e6ca21 100644 --- a/src-tauri/src/wfm_client/modules/auth.rs +++ b/src-tauri/src/wfm_client/modules/auth.rs @@ -4,13 +4,12 @@ use serde_json::json; use crate::{ auth::AuthState, - enums::LogLevel, error::{self, ApiResult, AppError}, - logger, wfm_client::client::WFMClient, }; pub struct AuthModule<'a> { pub client: &'a WFMClient, + pub debug_id: String, } impl<'a> AuthModule<'a> { @@ -27,6 +26,7 @@ impl<'a> AuthModule<'a> { { Ok(ApiResult::Success(user, headers)) => { self.client.debug( + &self.debug_id, "User:Login", format!("User logged in: {}", user.ingame_name).as_str(), None, @@ -36,8 +36,9 @@ impl<'a> AuthModule<'a> { Ok(ApiResult::Error(e, _headers)) => { return Err(self.client.create_api_error( "Auth:Login", - error, + e, eyre!("There was an error logging in"), + crate::enums::LogLevel::Error, )); } Err(e) => return Err(e), diff --git a/src-tauri/src/wfm_client/modules/chat.rs b/src-tauri/src/wfm_client/modules/chat.rs index 6a52ee53..c2ef5e58 100644 --- a/src-tauri/src/wfm_client/modules/chat.rs +++ b/src-tauri/src/wfm_client/modules/chat.rs @@ -1,26 +1,27 @@ -use std::f32::consts::E; - use eyre::eyre; use serde::{Deserialize, Serialize}; -use serde_json::json; use crate::{ - enums::LogLevel, error::{ApiResult, AppError}, - helper, logger, - structs::{Item, ItemDetails}, + helper, wfm_client::client::WFMClient, }; pub struct ChatModule<'a> { pub client: &'a WFMClient, + pub debug_id: String, } impl<'a> ChatModule<'a> { pub async fn get_chats(&self) -> Result, AppError> { - match self.client.get::>("im/chats", Some("chats")).await { + match self + .client + .get::>("im/chats", Some("chats")) + .await + { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Chat:GetChats", format!("{} was fetched.", payload.len()).as_str(), None, @@ -32,6 +33,7 @@ impl<'a> ChatModule<'a> { "Chat:GetChats", error, eyre!("There was an error fetching chats"), + crate::enums::LogLevel::Error, )); } Err(err) => { @@ -42,9 +44,14 @@ impl<'a> ChatModule<'a> { pub async fn get_chat(&self, id: String) -> Result, AppError> { let url = format!("im/chats/{}", id); - match self.client.get::>(&url, Some("messages")).await { + match self + .client + .get::>(&url, Some("messages")) + .await + { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Chat:GetChat", format!("{} chat messages were fetched.", payload.len()).as_str(), None, @@ -56,6 +63,7 @@ impl<'a> ChatModule<'a> { "Chat:GetChatById", error, eyre!("There was an error fetching chat messages for chat {}", id), + crate::enums::LogLevel::Error, )); } Err(err) => { @@ -69,6 +77,7 @@ impl<'a> ChatModule<'a> { match self.client.delete(&url, Some("chat_id")).await { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Chat:Delete", format!("Chat {} was deleted.", id).as_str(), None, @@ -80,6 +89,7 @@ impl<'a> ChatModule<'a> { "Chat:Delete", error, eyre!("There was an error deleting chat {}", id), + crate::enums::LogLevel::Error, )); } Err(err) => { diff --git a/src-tauri/src/wfm_client/modules/item.rs b/src-tauri/src/wfm_client/modules/item.rs index ad2cbe9e..fb5d78a0 100644 --- a/src-tauri/src/wfm_client/modules/item.rs +++ b/src-tauri/src/wfm_client/modules/item.rs @@ -1,5 +1,4 @@ use crate::{ - enums::LogLevel, error::{ApiResult, AppError}, structs::{Item, ItemDetails}, wfm_client::client::WFMClient, @@ -8,6 +7,7 @@ use crate::{ use eyre::eyre; pub struct ItemModule<'a> { pub client: &'a WFMClient, + pub debug_id: String, } impl<'a> ItemModule<'a> { @@ -15,6 +15,7 @@ impl<'a> ItemModule<'a> { match self.client.get::>("items", Some("items")).await { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Item:GetAllItems", format!("{} items were fetched.", payload.len()).as_str(), None, @@ -26,6 +27,7 @@ impl<'a> ItemModule<'a> { "Item:GetAllItems", error, eyre!("There was an error fetching items"), + crate::enums::LogLevel::Error, )); } Err(err) => { @@ -38,6 +40,7 @@ impl<'a> ItemModule<'a> { match self.client.get(&url, Some("item")).await { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Item:GetItem", format!("Gettting item: {}", item).as_str(), None, @@ -49,6 +52,7 @@ impl<'a> ItemModule<'a> { "Item:GetItem", error, eyre!("There was an error fetching item {}", item), + crate::enums::LogLevel::Error, )); } Err(err) => { diff --git a/src-tauri/src/wfm_client/modules/order.rs b/src-tauri/src/wfm_client/modules/order.rs index ea6481cd..68caa900 100644 --- a/src-tauri/src/wfm_client/modules/order.rs +++ b/src-tauri/src/wfm_client/modules/order.rs @@ -2,13 +2,12 @@ use polars::{ prelude::{DataFrame, NamedFrom}, series::Series, }; -use reqwest::header::HeaderMap; use serde_json::json; use crate::{ - enums::{LogLevel, OrderType}, + enums::OrderType, error::{ApiResult, AppError}, - helper, logger, + helper, structs::{Order, Ordres}, wfm_client::client::WFMClient, }; @@ -16,6 +15,7 @@ use crate::{ use eyre::eyre; pub struct OrderModule<'a> { pub client: &'a WFMClient, + pub debug_id: String, } impl<'a> OrderModule<'a> { @@ -25,6 +25,7 @@ impl<'a> OrderModule<'a> { match self.client.get::(&url, None).await { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Order:GetUserOrders", format!( "{} orders were fetched.", @@ -40,6 +41,7 @@ impl<'a> OrderModule<'a> { "Order:GetUserOrders", error, eyre!("There was an error fetching orders for {}", ingame_name), + crate::enums::LogLevel::Error, )); } Err(err) => { @@ -83,6 +85,7 @@ impl<'a> OrderModule<'a> { { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Order:Create", format!( "Order created type: {} item: {}, platinum: {}, quantity: {}, rank: {}", @@ -103,6 +106,7 @@ impl<'a> OrderModule<'a> { "Order:Create", error, eyre!("There was an error creating order"), + crate::enums::LogLevel::Error, )); } Err(err) => { @@ -116,6 +120,7 @@ impl<'a> OrderModule<'a> { match self.client.delete(&url, Some("order_id")).await { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Order:Delete", format!("Order {} was deleted.", order_id).as_str(), None, @@ -124,10 +129,17 @@ impl<'a> OrderModule<'a> { return Ok(payload); } Ok(ApiResult::Error(error, _headers)) => { + let log_level = match error.messages.get(0) { + Some(message) if message.contains("app.delete_order.order_not_exist") => { + crate::enums::LogLevel::Warning + } + _ => crate::enums::LogLevel::Error, + }; return Err(self.client.create_api_error( "Order:Delete", error, eyre!("There was an error deleting order {}", order_id), + crate::enums::LogLevel::Error, )); } Err(err) => { @@ -157,6 +169,7 @@ impl<'a> OrderModule<'a> { { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Order:Update", format!( "Order id: {}, platinum: {}, quantity: {}", @@ -173,6 +186,7 @@ impl<'a> OrderModule<'a> { "Order:Update", error, eyre!("There was an error updating order {}", order_id), + crate::enums::LogLevel::Error, )); } Err(err) => { @@ -206,6 +220,7 @@ impl<'a> OrderModule<'a> { match self.client.put(&url, Some("order"), None).await { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Order:Close", format!( "Order {} type: {} was closed.", @@ -218,10 +233,17 @@ impl<'a> OrderModule<'a> { payload } Ok(ApiResult::Error(error, _headers)) => { + let log_level = match error.messages.get(0) { + Some(message) if message.contains("app.close_order.order_not_exist") => { + crate::enums::LogLevel::Warning + } + _ => crate::enums::LogLevel::Error, + }; return Err(self.client.create_api_error( "Order:Closeing", error, eyre!("There was an error closing order {}", order.id), + crate::enums::LogLevel::Error, )); } Err(err) => { @@ -257,11 +279,12 @@ impl<'a> OrderModule<'a> { pub async fn get_ordres_by_item(&self, item: &str) -> Result { let url = format!("items/{}/orders", item); - let orders: Vec = match self.client.get(&url, Some("orders")).await { + let orders = match self.client.get::>(&url, Some("orders")).await { Ok(ApiResult::Success(payload, _headers)) => { self.client.debug( + &self.debug_id, "Order:GetOrdersByItem", - format!("Orders for {} were fetched.", item).as_str(), + format!("Orders for {} were fetched. found: {}", item, payload.len()).as_str(), None, ); payload @@ -271,6 +294,7 @@ impl<'a> OrderModule<'a> { "Order:GetOrdersByItem", error, eyre!("There was an error fetching orders for {}", item), + crate::enums::LogLevel::Error, )); } Err(err) => { diff --git a/src/api/index.ts b/src/api/index.ts index 81fab7f4..5d2ca0a4 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -28,6 +28,9 @@ const api = { getChat: async (id: string): Promise => { return await invoke("get_chat", { id }) as Wfm.ChatMessage[]; }, + on_new_wfm_message: async (message: Wfm.ChatMessage) => { + return await invoke("on_new_wfm_message", { message }); + }, }, debug: { importWarframeAlgoTraderData: async (dbPath: string, type: string): Promise => { diff --git a/src/components/modals/settings/index.tsx b/src/components/modals/settings/index.tsx index ec31b623..aa774c8f 100644 --- a/src/components/modals/settings/index.tsx +++ b/src/components/modals/settings/index.tsx @@ -3,9 +3,10 @@ import { GeneralPanel } from "./general.panel"; import { LiveScraperPanel } from "./liveScraper.panel"; import { useTranslateModal } from "@hooks/index"; import { DeepPartial, Settings, Wfm } from "$types/index"; -import { WhisperScraperPanel } from "./whisperScraper.panel"; +import { NotificationsPanel } from "./notifications.panel"; import { useState } from "react"; import { modals } from "@mantine/modals"; +import { LoggingPanel } from "./logging.panel"; interface SettingsModalProps { settings: Settings | undefined; @@ -29,7 +30,8 @@ export function SettingsModal({ tradable_items, settings: settingsIn, updateSett {useTranslateSettingsPanels("general.title")} {useTranslateSettingsPanels("live_trading.title")} - {useTranslateSettingsPanels("whisper_scraper.title")} + {useTranslateSettingsPanels("notifications.title")} + {useTranslateSettingsPanels("logging.title")} @@ -45,10 +47,17 @@ export function SettingsModal({ tradable_items, settings: settingsIn, updateSett }} tradable_items={tradable_items} /> - + - { - handleUpdateSettings({ whisper_scraper: set }) + { + handleUpdateSettings({ ...set }) + }} /> + + + + + { + handleUpdateSettings({ notifications: set }) }} tradable_items={tradable_items} /> diff --git a/src/components/modals/settings/logging.panel.tsx b/src/components/modals/settings/logging.panel.tsx new file mode 100644 index 00000000..449bac87 --- /dev/null +++ b/src/components/modals/settings/logging.panel.tsx @@ -0,0 +1,70 @@ +import { Button, Group, MultiSelect } from "@mantine/core"; +import { Settings } from "$types/index"; +import { useForm } from "@mantine/form"; +import { useEffect } from "react"; +import { useTranslateModal } from "../../../hooks"; +interface LoggingProps { + settings: Settings | undefined; + updateSettings: (user: Partial) => void; +} + +export function LoggingPanel({ updateSettings, settings }: LoggingProps) { + const roleForm = useForm({ + initialValues: { + debug: [] as string[], + }, + validate: {}, + }); + + + const useTranslateSettingsModal = (key: string, context?: { [key: string]: any }, i18Key?: boolean) => useTranslateModal(`settings.panels.logging.${key}`, { ...context }, i18Key) + const useTranslateFields = (key: string, context?: { [key: string]: any }, i18Key?: boolean) => useTranslateSettingsModal(`fields.${key}`, { ...context }, i18Key) + + useEffect(() => { + if (!settings) return; + // Set Settings from live Scraper + roleForm.setFieldValue("debug", settings.debug); + }, [settings]); + + return ( +
{ + console.log(data); + + updateSettings({ debug: data.debug }) + })}> + { + roleForm.setFieldValue("debug", value); + console.log(value); + }} + clearable + searchable + maw={400} + /> + + + + + ); + +} \ No newline at end of file diff --git a/src/components/modals/settings/notifications.panel.tsx b/src/components/modals/settings/notifications.panel.tsx new file mode 100644 index 00000000..e0a16687 --- /dev/null +++ b/src/components/modals/settings/notifications.panel.tsx @@ -0,0 +1,159 @@ +import { useEffect } from "react"; +import { useForm } from "@mantine/form"; +import { Button, Card, Collapse, Group, TextInput, Textarea, Text, Tooltip, ActionIcon, SimpleGrid, Divider } from "@mantine/core"; +import { useTranslateModal } from "@hooks/index"; +import { Notifications, Wfm, NotificationBase } from "$types/index"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faBell } from "@fortawesome/free-solid-svg-icons"; +import { faDiscord } from "@fortawesome/free-brands-svg-icons"; + +interface LiveScraperProps { + settings: Notifications | undefined; + tradable_items: Wfm.ItemDto[]; + updateSettings: (user: Partial) => void; +} + + +interface NotificationProps { + i18Key: string; + notifi: NotificationBase; + onChange: (key: string, event: string | boolean | string[]) => void; +} + +const Notification = ({ i18Key, notifi: conversation, onChange }: NotificationProps) => { + const useTranslateConversation = (key: string, context?: { [key: string]: any }) => useTranslateModal(`${i18Key}.${key}`, { ...context }) + const useTranslateFields = (key: string, context?: { [key: string]: any }) => useTranslateConversation(`fields.${key}`, { ...context }) + return ( + + + {useTranslateConversation("title")} + + + onChange('system_notify', !conversation.system_notify)} > + + + + + onChange('discord_notify', !conversation.discord_notify)} > + + + + + + + + + + onChange('title', event.currentTarget.value)} + /> + + + +