2023-05-27 23:21:50 +00:00
|
|
|
use crate::{
|
2023-06-23 09:36:36 +00:00
|
|
|
errors::{HandlerUsed, NoUserInfo},
|
2023-06-23 08:45:37 +00:00
|
|
|
markups::deletion_markup,
|
|
|
|
models::{DecryptedAccount, User},
|
|
|
|
utils::delete_optional,
|
2023-06-01 11:42:35 +00:00
|
|
|
MainDialogue,
|
2023-05-27 23:21:50 +00:00
|
|
|
};
|
2023-06-23 09:36:36 +00:00
|
|
|
use entity::prelude::*;
|
|
|
|
use futures::{future::try_join, TryStreamExt};
|
2023-06-23 08:45:37 +00:00
|
|
|
use itertools::Itertools;
|
2023-06-23 09:36:36 +00:00
|
|
|
use rustc_hash::FxHashSet;
|
2023-05-27 23:21:50 +00:00
|
|
|
use sea_orm::prelude::*;
|
2023-06-23 08:45:37 +00:00
|
|
|
use std::fmt::Write;
|
|
|
|
use teloxide::{adaptors::Throttle, net::Download, prelude::*, types::Document};
|
2023-05-27 23:21:50 +00:00
|
|
|
use tokio::task::spawn_blocking;
|
2023-06-23 08:45:37 +00:00
|
|
|
use trim_in_place::TrimInPlace;
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
async fn download_file(bot: &Throttle<Bot>, document: &Document) -> crate::Result<Vec<u8>> {
|
|
|
|
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]
|
2023-06-23 09:36:36 +00:00
|
|
|
fn process_accounts(
|
|
|
|
accounts: &mut [DecryptedAccount],
|
|
|
|
existing_names: FxHashSet<String>,
|
|
|
|
) -> crate::Result<Result<(), String>> {
|
2023-06-23 08:45:37 +00:00
|
|
|
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();
|
2023-06-23 09:36:36 +00:00
|
|
|
let mut existing = Vec::new();
|
2023-06-23 08:45:37 +00:00
|
|
|
let mut invalid = Vec::new();
|
|
|
|
|
|
|
|
accounts
|
|
|
|
.iter()
|
|
|
|
.dedup_by_with_count(|a, b| a.name == b.name)
|
|
|
|
.for_each(|(count, account)| {
|
2023-06-23 09:36:36 +00:00
|
|
|
let duplicate = count != 1;
|
|
|
|
let exists = existing_names.contains(&account.name);
|
|
|
|
if duplicate {
|
2023-06-23 08:45:37 +00:00
|
|
|
duplicates.push(account.name.as_str());
|
2023-06-23 09:36:36 +00:00
|
|
|
}
|
|
|
|
if exists {
|
|
|
|
existing.push(account.name.as_str())
|
|
|
|
}
|
|
|
|
// If it already exists or if it is a duplicate there's no need to check the account's validity
|
|
|
|
if !duplicate && !exists && !account.validate() {
|
2023-06-23 08:45:37 +00:00
|
|
|
invalid.push(account.name.as_str());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-06-23 09:36:36 +00:00
|
|
|
drop(existing_names);
|
|
|
|
|
|
|
|
if duplicates.is_empty() && invalid.is_empty() && existing.is_empty() {
|
2023-06-23 08:45:37 +00:00
|
|
|
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")
|
|
|
|
)?
|
|
|
|
}
|
|
|
|
|
2023-06-23 09:36:36 +00:00
|
|
|
if !existing.is_empty() {
|
|
|
|
write!(
|
|
|
|
error_text,
|
|
|
|
"\n\nAccounts with these names already exist in the database:\n{:?}",
|
|
|
|
existing.into_iter().format("\n")
|
|
|
|
)?
|
|
|
|
}
|
|
|
|
|
2023-06-23 08:45:37 +00:00
|
|
|
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]
|
2023-06-23 09:36:36 +00:00
|
|
|
fn user_from_vec(
|
|
|
|
vector: Vec<u8>,
|
|
|
|
existing_names: FxHashSet<String>,
|
|
|
|
) -> crate::Result<Result<User, String>> {
|
2023-06-23 08:45:37 +00:00
|
|
|
let mut user: User = serde_json::from_slice(&vector)?;
|
|
|
|
drop(vector);
|
2023-06-23 09:36:36 +00:00
|
|
|
match process_accounts(&mut user.accounts, existing_names)? {
|
2023-06-23 08:45:37 +00:00
|
|
|
Ok(()) => Ok(Ok(user)),
|
|
|
|
Err(error_text) => Ok(Err(error_text)),
|
|
|
|
}
|
|
|
|
}
|
2023-05-27 23:21:50 +00:00
|
|
|
|
|
|
|
/// Function to handle GetUser state. It doesn't actually validate anything
|
|
|
|
pub async fn get_user(
|
|
|
|
bot: Throttle<Bot>,
|
|
|
|
msg: Message,
|
|
|
|
db: DatabaseConnection,
|
|
|
|
dialogue: MainDialogue,
|
|
|
|
next: super::PackagedHandler<User>,
|
|
|
|
) -> crate::Result<()> {
|
2023-06-23 09:36:36 +00:00
|
|
|
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
2023-05-27 23:21:50 +00:00
|
|
|
let mut handler = next.lock().await;
|
|
|
|
delete_optional(&bot, handler.previous.as_ref()).await;
|
|
|
|
|
2023-06-23 10:25:41 +00:00
|
|
|
if let Some("/cancel") = msg.text().map(str::trim) {
|
2023-05-27 23:21:50 +00:00
|
|
|
dialogue.exit().await?;
|
|
|
|
bot.send_message(msg.chat.id, "Successfully cancelled")
|
|
|
|
.reply_markup(deletion_markup())
|
|
|
|
.await?;
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
if handler.func.is_none() {
|
|
|
|
let _ = dialogue.exit().await;
|
|
|
|
return Err(HandlerUsed.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
let document = match msg.document() {
|
|
|
|
Some(document) => document,
|
|
|
|
None => {
|
|
|
|
let msg = bot
|
|
|
|
.send_message(msg.chat.id, "You didn't send a file. Try again")
|
|
|
|
.await?;
|
|
|
|
handler.previous = Some(msg);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
match document.file_name.as_deref() {
|
|
|
|
Some(name) if name.trim_end().ends_with(".json") => (),
|
|
|
|
_ => {
|
|
|
|
let msg = bot
|
|
|
|
.send_message(
|
|
|
|
msg.chat.id,
|
|
|
|
"Invalid file name. You need to send a json file. Try again",
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
handler.previous = Some(msg);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-23 09:36:36 +00:00
|
|
|
let existing_names = async {
|
|
|
|
Account::get_names(user_id, &db)
|
|
|
|
.await?
|
|
|
|
.try_collect::<FxHashSet<_>>()
|
|
|
|
.await
|
|
|
|
.map_err(Into::into)
|
|
|
|
};
|
|
|
|
|
|
|
|
let (data, existing_names) = try_join(download_file(&bot, document), existing_names).await?;
|
2023-05-27 23:21:50 +00:00
|
|
|
|
2023-06-23 09:36:36 +00:00
|
|
|
let user = match spawn_blocking(move || user_from_vec(data, existing_names)).await? {
|
2023-06-23 08:45:37 +00:00
|
|
|
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(());
|
|
|
|
}
|
2023-05-27 23:21:50 +00:00
|
|
|
Err(_) => {
|
|
|
|
let msg = bot
|
|
|
|
.send_message(msg.chat.id, "Error parsing the json file. Try again")
|
|
|
|
.await?;
|
|
|
|
handler.previous = Some(msg);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let func = handler.func.take().unwrap();
|
|
|
|
drop(handler);
|
|
|
|
|
|
|
|
if let Err(err) = func(bot, msg, db, dialogue.clone(), user).await {
|
|
|
|
let _ = dialogue.exit().await;
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|