diff --git a/Cargo.lock b/Cargo.lock index a27a621..a1fd65e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,9 +57,9 @@ checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" [[package]] name = "allocator-api2" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f263788a35611fba42eb41ff811c5d0360c58b97402570312a350736e2542e" +checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" [[package]] name = "android-tzdata" @@ -97,9 +97,9 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8868f09ff8cea88b079da74ae569d9b8c62a23c68c746240b704ee6f7525c89c" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-stream" @@ -383,9 +383,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" dependencies = [ "libc", ] @@ -930,7 +930,7 @@ checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" dependencies = [ "http", "hyper", - "rustls 0.21.1", + "rustls 0.21.2", "tokio", "tokio-rustls 0.24.1", ] @@ -1351,6 +1351,7 @@ dependencies = [ "teloxide", "thiserror", "tokio", + "trim-in-place", ] [[package]] @@ -1675,7 +1676,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.1", + "rustls 0.21.2", "rustls-pemfile", "serde", "serde_json", @@ -1758,9 +1759,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.29.1" +version = "1.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26bd36b60561ee1fb5ec2817f198b6fd09fa571c897a5e86d1487cfc2b096dfc" +checksum = "d0446843641c69436765a35a5a77088e28c2e6a12da93e84aa3ab1cd4aa5a042" dependencies = [ "arrayvec", "borsh", @@ -1811,9 +1812,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" +checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f" dependencies = [ "log", "ring", @@ -2067,9 +2068,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" dependencies = [ "itoa", "ryu", @@ -2113,9 +2114,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", @@ -2552,7 +2553,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.1", + "rustls 0.21.2", "tokio", ] @@ -2611,9 +2612,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", @@ -2644,6 +2645,12 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + [[package]] name = "try-lock" version = "0.2.4" @@ -2750,11 +2757,10 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] diff --git a/Cargo.toml b/Cargo.toml index 486fab4..7c2881b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,4 @@ serde_json = "1.0.96" teloxide = { version = "0.12.2", features = ["macros", "ctrlc_handler", "rustls", "throttle"], default-features = false } thiserror = "1.0.40" tokio = { version = "1.28.1", features = ["macros", "rt-multi-thread"] } +trim-in-place = "0.1.7" diff --git a/src/models.rs b/src/models.rs index f5739de..abebf25 100644 --- a/src/models.rs +++ b/src/models.rs @@ -45,9 +45,13 @@ impl DecryptedAccount { /// Returns true if the account's fields are valid #[inline] pub fn validate(&self) -> bool { - [&self.name, &self.login, &self.password] - .into_iter() - .all(|field| validate_field(field)) + [ + self.name.as_str(), + self.login.as_str(), + self.password.as_str(), + ] + .into_iter() + .all(validate_field) } } diff --git a/src/state/get_user.rs b/src/state/get_user.rs index bec4b7d..9bcf262 100644 --- a/src/state/get_user.rs +++ b/src/state/get_user.rs @@ -1,11 +1,91 @@ use crate::{ - errors::HandlerUsed, markups::deletion_markup, models::User, utils::delete_optional, + errors::HandlerUsed, + markups::deletion_markup, + models::{DecryptedAccount, User}, + utils::delete_optional, MainDialogue, }; use futures::TryStreamExt; +use itertools::Itertools; use sea_orm::prelude::*; -use teloxide::{adaptors::Throttle, net::Download, prelude::*}; +use std::fmt::Write; +use teloxide::{adaptors::Throttle, net::Download, prelude::*, types::Document}; use tokio::task::spawn_blocking; +use trim_in_place::TrimInPlace; + +#[inline] +async fn download_file(bot: &Throttle, document: &Document) -> crate::Result> { + let path = bot.get_file(document.file.id.as_str()).await?.path; + let mut data = Vec::with_capacity(document.file.size as usize); + bot.download_file_stream(&path) + .try_for_each(|bytes| { + data.extend(bytes); + async { Ok(()) } + }) + .await?; + Ok(data) +} + +#[inline] +fn process_accounts(accounts: &mut [DecryptedAccount]) -> crate::Result> { + for account in accounts.iter_mut() { + account.name.trim_in_place(); + account.login.trim_in_place(); + account.password.trim_in_place(); + } + + accounts.sort_unstable_by(|a, b| a.name.cmp(&b.name)); + + let mut duplicates = Vec::new(); + let mut invalid = Vec::new(); + + accounts + .iter() + .dedup_by_with_count(|a, b| a.name == b.name) + .for_each(|(count, account)| { + if count != 1 { + duplicates.push(account.name.as_str()); + } else if !account.validate() { + invalid.push(account.name.as_str()); + } + }); + + if duplicates.is_empty() && invalid.is_empty() { + return Ok(Ok(())); + } + + let mut error_text = "Your accounts have the following problems:".to_owned(); + + if !duplicates.is_empty() { + write!( + error_text, + "\n\nDuplicate names:\n{:?}", + duplicates.into_iter().format("\n") + )? + } + + if !invalid.is_empty() { + write!( + error_text, + "\n\nInvalid account fields:\n{:?}", + invalid.into_iter().format("\n") + )? + } + + error_text.push_str("\n\nFix these problems and send the file again"); + + Ok(Err(error_text)) +} + +#[inline] +fn user_from_vec(vector: Vec) -> crate::Result> { + let mut user: User = serde_json::from_slice(&vector)?; + drop(vector); + match process_accounts(&mut user.accounts)? { + Ok(()) => Ok(Ok(user)), + Err(error_text) => Ok(Err(error_text)), + } +} /// Function to handle GetUser state. It doesn't actually validate anything pub async fn get_user( @@ -56,17 +136,15 @@ pub async fn get_user( } } - let file = bot.get_file(&document.file.id).await?; - let mut data = Vec::with_capacity(document.file.size as usize); - bot.download_file_stream(&file.path) - .try_for_each(|bytes| { - data.extend(bytes); - async { Ok(()) } - }) - .await?; + let data = download_file(&bot, document).await?; - let user: User = match spawn_blocking(move || serde_json::from_slice(&data)).await? { - Ok(user) => user, + let user = match spawn_blocking(move || user_from_vec(data)).await? { + Ok(Ok(user)) => user, + Ok(Err(error_text)) => { + let msg = bot.send_message(msg.chat.id, error_text).await?; + handler.previous = Some(msg); + return Ok(()); + } Err(_) => { let msg = bot .send_message(msg.chat.id, "Error parsing the json file. Try again") diff --git a/src/utils.rs b/src/utils.rs index 4d9495c..d41b1e1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -17,7 +17,10 @@ pub async fn delete_optional(bot: &Throttle, msg: Option<&Message>) { /// Returns true if the field is valid #[inline] pub fn validate_field(field: &str) -> bool { + if field.is_empty() { + return false; + } field .chars() - .all(|char| char != '`' && char != '\\' && char != '\n') + .all(|char| !['`', '\\', '\n', '\t'].contains(&char)) }