Added support for multiple languages
This commit is contained in:
@ -1,22 +1,31 @@
|
||||
//! This module consists of endpoints to handle callbacks
|
||||
|
||||
crate::export_handlers!(decrypt, delete, delete_message, get, get_menu, alter);
|
||||
crate::export_handlers!(
|
||||
decrypt,
|
||||
delete,
|
||||
delete_message,
|
||||
get,
|
||||
get_menu,
|
||||
alter,
|
||||
change_locale
|
||||
);
|
||||
|
||||
use crate::errors::InvalidCommand;
|
||||
use crate::{errors::InvalidCommand, locales::LocaleTypeExt};
|
||||
use base64::{engine::general_purpose::STANDARD_NO_PAD as B64_ENGINE, Engine as _};
|
||||
use entity::locale::LocaleType;
|
||||
use std::str::FromStr;
|
||||
use teloxide::types::CallbackQuery;
|
||||
|
||||
type NameHash = Vec<u8>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AlterableField {
|
||||
Name,
|
||||
Login,
|
||||
Pass,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone)]
|
||||
pub enum CallbackCommand {
|
||||
DeleteMessage,
|
||||
Get(NameHash),
|
||||
@ -25,12 +34,13 @@ pub enum CallbackCommand {
|
||||
Hide(NameHash),
|
||||
Alter(NameHash, AlterableField),
|
||||
DeleteAccount { name: NameHash, is_command: bool },
|
||||
ChangeLocale(LocaleType),
|
||||
}
|
||||
|
||||
impl CallbackCommand {
|
||||
pub fn from_query(q: CallbackQuery) -> Option<Self> {
|
||||
q.message.as_ref()?;
|
||||
q.data.and_then(|data| data.parse().ok())
|
||||
q.message?;
|
||||
q.data?.parse().inspect_err(|err| log::error!("{err}")).ok()
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,13 +55,19 @@ impl FromStr for CallbackCommand {
|
||||
};
|
||||
|
||||
let mut substrings = s.split(' ');
|
||||
let (Some(command), Some(name), None) =
|
||||
let (Some(command), Some(param), None) =
|
||||
(substrings.next(), substrings.next(), substrings.next())
|
||||
else {
|
||||
return Err(InvalidCommand::InvalidParams);
|
||||
};
|
||||
|
||||
let name_hash = B64_ENGINE.decode(name)?;
|
||||
if command == "change_locale" {
|
||||
let locale =
|
||||
LocaleType::from_language_code(param).ok_or(InvalidCommand::UnknownLocale)?;
|
||||
return Ok(Self::ChangeLocale(locale));
|
||||
}
|
||||
|
||||
let name_hash = B64_ENGINE.decode(param)?;
|
||||
if name_hash.len() != 32 {
|
||||
return Err(InvalidCommand::InvalidOutputLength);
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ async fn get_master_pass(
|
||||
db: Pool,
|
||||
dialogue: MainDialogue,
|
||||
mut ids: MessageIds,
|
||||
locale: LocaleRef,
|
||||
name: String,
|
||||
field: AlterableField,
|
||||
field_value: String,
|
||||
@ -57,7 +58,7 @@ async fn get_master_pass(
|
||||
|
||||
ids.alter_message(
|
||||
&bot,
|
||||
"Success. Choose the account to view",
|
||||
locale.success_choose_account_to_view.as_ref(),
|
||||
menu_markup("get", user_id, &db).await?,
|
||||
None,
|
||||
)
|
||||
@ -66,7 +67,7 @@ async fn get_master_pass(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
handler!(get_field(name:String, field:AlterableField, field_value:String), "Send the master password", State::GetMasterPass, get_master_pass);
|
||||
handler!(get_field(name:String, field:AlterableField, field_value:String),send_master_password, State::GetMasterPass, get_master_pass);
|
||||
|
||||
#[inline]
|
||||
pub async fn alter(
|
||||
@ -74,6 +75,7 @@ pub async fn alter(
|
||||
q: CallbackQuery,
|
||||
db: Pool,
|
||||
dialogue: MainDialogue,
|
||||
locale: LocaleRef,
|
||||
(hash, field): (super::NameHash, AlterableField),
|
||||
) -> crate::Result<()> {
|
||||
let user_id = q.from.id.0;
|
||||
@ -81,7 +83,7 @@ pub async fn alter(
|
||||
|
||||
let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else {
|
||||
bot.send_message(ids.0, "Account wasn't found")
|
||||
.reply_markup(deletion_markup())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
bot.answer_callback_query(q.id).await?;
|
||||
return Ok(());
|
||||
@ -90,15 +92,15 @@ pub async fn alter(
|
||||
let text = match field {
|
||||
Name => {
|
||||
change_state!(dialogue, ids, (name, field), State::GetNewName, get_field);
|
||||
"Send new account name"
|
||||
locale.send_new_name.as_ref()
|
||||
}
|
||||
Login => {
|
||||
change_state!(dialogue, ids, (name, field), State::GetLogin, get_field);
|
||||
"Send new account login"
|
||||
locale.send_new_login.as_ref()
|
||||
}
|
||||
Pass => {
|
||||
change_state!(dialogue, ids, (name, field), State::GetPassword, get_field);
|
||||
"Send new account password"
|
||||
locale.send_new_password.as_ref()
|
||||
}
|
||||
};
|
||||
|
||||
|
42
src/callbacks/change_locale.rs
Normal file
42
src/callbacks/change_locale.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use crate::{locales::LocaleTypeExt, prelude::*};
|
||||
use entity::locale::LocaleType;
|
||||
|
||||
#[inline]
|
||||
pub async fn change_locale(
|
||||
bot: Throttle<Bot>,
|
||||
q: CallbackQuery,
|
||||
db: Pool,
|
||||
mut locale: LocaleRef,
|
||||
new_locale: LocaleType,
|
||||
) -> crate::Result<()> {
|
||||
let mut ids: MessageIds = q.message.as_ref().unwrap().into();
|
||||
let user_id = q.from.id.0;
|
||||
|
||||
let is_successful = new_locale
|
||||
.update(user_id, &db)
|
||||
.await
|
||||
.inspect_err(|err| log::error!("{err}"))
|
||||
.unwrap_or(false);
|
||||
|
||||
if !is_successful {
|
||||
ids.alter_message(
|
||||
&bot,
|
||||
locale.something_went_wrong.as_ref(),
|
||||
deletion_markup(locale),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
locale = new_locale.get_locale();
|
||||
ids.alter_message(
|
||||
&bot,
|
||||
locale.choose_language.as_ref(),
|
||||
language_markup(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -3,12 +3,14 @@ use teloxide::types::ParseMode;
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn get_master_pass(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: Pool,
|
||||
dialogue: MainDialogue,
|
||||
mut ids: MessageIds,
|
||||
locale: LocaleRef,
|
||||
name: String,
|
||||
master_pass: String,
|
||||
) -> crate::Result<()> {
|
||||
@ -17,8 +19,8 @@ async fn get_master_pass(
|
||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||
|
||||
let Some(account) = Account::get(user_id, &name, &db).await? else {
|
||||
bot.send_message(msg.chat.id, "Account not found")
|
||||
.reply_markup(deletion_markup())
|
||||
bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
return Ok(());
|
||||
};
|
||||
@ -26,15 +28,12 @@ async fn get_master_pass(
|
||||
let account =
|
||||
spawn_blocking(move || DecryptedAccount::from_account(account, &master_pass)).await??;
|
||||
|
||||
let text = format!(
|
||||
"Name:\n`{name}`\nLogin:\n`{}`\nPassword:\n`{}`",
|
||||
account.login, account.password
|
||||
);
|
||||
let text = locale.show_account(&account.name, &account.login, &account.password);
|
||||
|
||||
ids.alter_message(
|
||||
&bot,
|
||||
text,
|
||||
account_markup(&name, false),
|
||||
account_markup(&name, false, locale),
|
||||
ParseMode::MarkdownV2,
|
||||
)
|
||||
.await?;
|
||||
@ -47,20 +46,21 @@ pub async fn decrypt(
|
||||
q: CallbackQuery,
|
||||
db: Pool,
|
||||
dialogue: MainDialogue,
|
||||
locale: LocaleRef,
|
||||
hash: super::NameHash,
|
||||
) -> crate::Result<()> {
|
||||
let mut ids: MessageIds = q.message.as_ref().unwrap().into();
|
||||
let user_id = q.from.id.0;
|
||||
|
||||
let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else {
|
||||
bot.send_message(ids.0, "Account wasn't found")
|
||||
.reply_markup(deletion_markup())
|
||||
bot.send_message(ids.0, locale.no_accounts_found.as_ref())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
bot.answer_callback_query(q.id).await?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
ids.alter_message(&bot, "Send master password", None, None)
|
||||
ids.alter_message(&bot, locale.send_master_password.as_ref(), None, None)
|
||||
.await?;
|
||||
bot.answer_callback_query(q.id).await?;
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
use crate::{change_state, prelude::*};
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn get_master_pass(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: Pool,
|
||||
dialogue: MainDialogue,
|
||||
mut ids: MessageIds,
|
||||
locale: LocaleRef,
|
||||
name: String,
|
||||
_: String,
|
||||
) -> crate::Result<()> {
|
||||
@ -15,13 +17,8 @@ async fn get_master_pass(
|
||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||
Account::delete(user_id, &name, &db).await?;
|
||||
|
||||
ids.alter_message(
|
||||
&bot,
|
||||
"The account is successfully deleted",
|
||||
deletion_markup(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
ids.alter_message(&bot, locale.success.as_ref(), deletion_markup(locale), None)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -32,26 +29,26 @@ pub async fn delete(
|
||||
q: CallbackQuery,
|
||||
db: Pool,
|
||||
dialogue: MainDialogue,
|
||||
locale: LocaleRef,
|
||||
(hash, is_command): (super::NameHash, bool),
|
||||
) -> crate::Result<()> {
|
||||
const TEXT: &str = "Send master password. \
|
||||
Once you send the master password the account is unrecoverable";
|
||||
let text = locale.send_master_pass_to_delete_account.as_ref();
|
||||
|
||||
let mut ids: MessageIds = q.message.as_ref().unwrap().into();
|
||||
let user_id = q.from.id.0;
|
||||
|
||||
let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else {
|
||||
bot.send_message(ids.0, "Account wasn't found")
|
||||
.reply_markup(deletion_markup())
|
||||
bot.send_message(ids.0, locale.no_accounts_found.as_ref())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
bot.answer_callback_query(q.id).await?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if is_command {
|
||||
ids.alter_message(&bot, TEXT, None, None).await?;
|
||||
ids.alter_message(&bot, text, None, None).await?;
|
||||
} else {
|
||||
let msg = bot.send_message(ids.0, TEXT).await?;
|
||||
let msg = bot.send_message(ids.0, text).await?;
|
||||
ids = MessageIds::from(&msg);
|
||||
};
|
||||
|
||||
|
@ -2,11 +2,15 @@ use crate::prelude::*;
|
||||
|
||||
/// Deletes the message from the callback
|
||||
#[inline]
|
||||
pub async fn delete_message(bot: Throttle<Bot>, q: CallbackQuery) -> crate::Result<()> {
|
||||
pub async fn delete_message(
|
||||
bot: Throttle<Bot>,
|
||||
q: CallbackQuery,
|
||||
locale: LocaleRef,
|
||||
) -> crate::Result<()> {
|
||||
if let Some(msg) = q.message {
|
||||
if bot.delete_message(msg.chat.id, msg.id).await.is_err() {
|
||||
bot.send_message(msg.chat.id, "Error deleting the message")
|
||||
.reply_markup(deletion_markup())
|
||||
bot.send_message(msg.chat.id, locale.error_deleting_message.as_ref())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
@ -6,25 +6,26 @@ pub async fn get(
|
||||
bot: Throttle<Bot>,
|
||||
q: CallbackQuery,
|
||||
db: Pool,
|
||||
locale: LocaleRef,
|
||||
hash: super::NameHash,
|
||||
) -> crate::Result<()> {
|
||||
let user_id = q.from.id.0;
|
||||
let mut ids: MessageIds = q.message.as_ref().unwrap().into();
|
||||
|
||||
let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else {
|
||||
bot.send_message(ids.0, "Account wasn't found")
|
||||
.reply_markup(deletion_markup())
|
||||
bot.send_message(ids.0, locale.account_not_found.as_ref())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
bot.answer_callback_query(q.id).await?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let text = format!("Name:\n`{name}`\nLogin:\n\\*\\*\\*\nPassword:\n\\*\\*\\*");
|
||||
let text = locale.show_hidden_account(&name);
|
||||
|
||||
ids.alter_message(
|
||||
&bot,
|
||||
text,
|
||||
account_markup(&name, true),
|
||||
account_markup(&name, true, locale),
|
||||
ParseMode::MarkdownV2,
|
||||
)
|
||||
.await?;
|
||||
|
@ -1,18 +1,28 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[inline]
|
||||
pub async fn get_menu(bot: Throttle<Bot>, q: CallbackQuery, db: Pool) -> crate::Result<()> {
|
||||
pub async fn get_menu(
|
||||
bot: Throttle<Bot>,
|
||||
q: CallbackQuery,
|
||||
db: Pool,
|
||||
locale: LocaleRef,
|
||||
) -> crate::Result<()> {
|
||||
let user_id = q.from.id.0;
|
||||
let mut ids: MessageIds = q.message.as_ref().unwrap().into();
|
||||
|
||||
let markup = menu_markup("get", user_id, &db).await?;
|
||||
if markup.inline_keyboard.is_empty() {
|
||||
ids.alter_message(&bot, "You don't have any accounts", deletion_markup(), None)
|
||||
.await?;
|
||||
ids.alter_message(
|
||||
&bot,
|
||||
locale.no_accounts_found.as_ref(),
|
||||
deletion_markup(locale),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ids.alter_message(&bot, "Choose your account", markup, None)
|
||||
ids.alter_message(&bot, locale.choose_account.as_ref(), markup, None)
|
||||
.await?;
|
||||
bot.answer_callback_query(q.id).await?;
|
||||
Ok(())
|
||||
|
@ -13,41 +13,27 @@ crate::export_handlers!(
|
||||
import,
|
||||
menu,
|
||||
set_master_pass,
|
||||
start
|
||||
start,
|
||||
change_language
|
||||
);
|
||||
|
||||
use teloxide::macros::BotCommands;
|
||||
|
||||
#[derive(BotCommands, Clone, Copy)]
|
||||
#[command(
|
||||
rename_rule = "snake_case",
|
||||
description = "These commands are supported:"
|
||||
)]
|
||||
#[command(rename_rule = "snake_case")]
|
||||
pub enum Command {
|
||||
#[command(description = "displays the welcome message")]
|
||||
Start,
|
||||
#[command(description = "displays this text")]
|
||||
Help,
|
||||
#[command(description = "sets the master password")]
|
||||
SetMasterPass,
|
||||
#[command(description = "gives you a menu to manage your accounts")]
|
||||
Menu,
|
||||
#[command(description = "adds the account")]
|
||||
AddAccount,
|
||||
#[command(description = "gets the account")]
|
||||
GetAccount,
|
||||
#[command(description = "gets a list of accounts")]
|
||||
GetAccounts,
|
||||
#[command(description = "deletes the account")]
|
||||
Delete,
|
||||
#[command(description = "deletes all the accounts and the master password")]
|
||||
DeleteAll,
|
||||
#[command(description = "exports all the accounts in a json file")]
|
||||
Export,
|
||||
#[command(description = "loads the accounts from a json file")]
|
||||
Import,
|
||||
#[command(description = "generates 10 secure passwords")]
|
||||
GenPassword,
|
||||
#[command(description = "cancels the current action")]
|
||||
Cancel,
|
||||
ChangeLanguage,
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ async fn get_master_pass(
|
||||
db: Pool,
|
||||
dialogue: MainDialogue,
|
||||
mut ids: MessageIds,
|
||||
locale: LocaleRef,
|
||||
name: String,
|
||||
login: String,
|
||||
password: String,
|
||||
@ -30,26 +31,26 @@ async fn get_master_pass(
|
||||
.await?;
|
||||
account.insert(&db).await?;
|
||||
|
||||
ids.alter_message(&bot, "Success", deletion_markup(), None)
|
||||
ids.alter_message(&bot, locale.success.as_ref(), deletion_markup(locale), None)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
handler!(
|
||||
get_password(name:String, login: String, password: String),
|
||||
"Send master password",
|
||||
send_master_password,
|
||||
State::GetMasterPass,
|
||||
get_master_pass
|
||||
);
|
||||
handler!(get_login(name: String, login: String),
|
||||
"Send password",
|
||||
send_password,
|
||||
State::GetPassword,
|
||||
get_password
|
||||
);
|
||||
handler!(get_account_name(name: String), "Send login", State::GetLogin, get_login);
|
||||
handler!(get_account_name(name: String), send_login, State::GetLogin, get_login);
|
||||
first_handler!(
|
||||
add_account,
|
||||
"Send account name",
|
||||
send_account_name,
|
||||
State::GetNewName,
|
||||
get_account_name
|
||||
);
|
||||
|
@ -2,9 +2,9 @@ use crate::prelude::*;
|
||||
|
||||
/// Handles /cancel command when there's no active state
|
||||
#[inline]
|
||||
pub async fn cancel(bot: Throttle<Bot>, msg: Message) -> crate::Result<()> {
|
||||
bot.send_message(msg.chat.id, "Nothing to cancel")
|
||||
.reply_markup(deletion_markup())
|
||||
pub async fn cancel(bot: Throttle<Bot>, msg: Message, locale: LocaleRef) -> crate::Result<()> {
|
||||
bot.send_message(msg.chat.id, locale.nothing_to_cancel.as_ref())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
13
src/commands/change_language.rs
Normal file
13
src/commands/change_language.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[inline]
|
||||
pub async fn change_language(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
locale: LocaleRef,
|
||||
) -> crate::Result<()> {
|
||||
bot.send_message(msg.chat.id, locale.choose_language.as_ref())
|
||||
.reply_markup(language_markup())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
@ -1,19 +1,24 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[inline]
|
||||
pub async fn delete(bot: Throttle<Bot>, msg: Message, db: Pool) -> crate::Result<()> {
|
||||
pub async fn delete(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: Pool,
|
||||
locale: LocaleRef,
|
||||
) -> crate::Result<()> {
|
||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||
|
||||
let markup = menu_markup("delete1", user_id, &db).await?;
|
||||
|
||||
if markup.inline_keyboard.is_empty() {
|
||||
bot.send_message(msg.chat.id, "You don't have any accounts")
|
||||
.reply_markup(deletion_markup())
|
||||
bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
bot.send_message(msg.chat.id, "Choose the account to delete")
|
||||
bot.send_message(msg.chat.id, locale.choose_account.as_ref())
|
||||
.reply_markup(markup)
|
||||
.await?;
|
||||
Ok(())
|
||||
|
@ -10,6 +10,7 @@ async fn get_master_pass(
|
||||
db: Pool,
|
||||
dialogue: MainDialogue,
|
||||
mut ids: MessageIds,
|
||||
locale: LocaleRef,
|
||||
_: String,
|
||||
) -> crate::Result<()> {
|
||||
dialogue.exit().await?;
|
||||
@ -23,22 +24,22 @@ async fn get_master_pass(
|
||||
let text = match result {
|
||||
(Ok(()), Ok(())) => {
|
||||
txn.commit().await?;
|
||||
"Everything was deleted"
|
||||
locale.everything_was_deleted.as_ref()
|
||||
}
|
||||
(Err(err), _) | (_, Err(err)) => {
|
||||
error!("{}", crate::Error::from(err));
|
||||
txn.rollback().await?;
|
||||
"Something went wrong. Try again later"
|
||||
locale.something_went_wrong.as_ref()
|
||||
}
|
||||
};
|
||||
ids.alter_message(&bot, text, deletion_markup(), None)
|
||||
ids.alter_message(&bot, text, deletion_markup(locale), None)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
first_handler!(
|
||||
delete_all,
|
||||
"Send master password to delete EVERYTHING.\nTHIS ACTION IS IRREVERSIBLE",
|
||||
send_master_pass_to_delete_everything,
|
||||
State::GetMasterPass,
|
||||
get_master_pass
|
||||
);
|
||||
|
@ -25,6 +25,7 @@ async fn get_master_pass(
|
||||
db: Pool,
|
||||
dialogue: MainDialogue,
|
||||
ids: MessageIds,
|
||||
locale: LocaleRef,
|
||||
master_pass: String,
|
||||
) -> crate::Result<()> {
|
||||
dialogue.exit().await?;
|
||||
@ -51,14 +52,14 @@ async fn get_master_pass(
|
||||
let file = InputFile::memory(json).file_name("accounts.json");
|
||||
|
||||
bot.send_document(msg.chat.id, file)
|
||||
.reply_markup(deletion_markup())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
first_handler!(
|
||||
export,
|
||||
"Send the master password to export your accounts",
|
||||
send_master_password,
|
||||
State::GetMasterPass,
|
||||
get_master_pass
|
||||
);
|
||||
|
@ -15,7 +15,11 @@ const BUFFER_LENGTH: usize =
|
||||
|
||||
/// Handles /`gen_password` command by generating 10 copyable passwords and sending them to the user
|
||||
#[inline]
|
||||
pub async fn gen_password(bot: Throttle<Bot>, msg: Message) -> crate::Result<()> {
|
||||
pub async fn gen_password(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
locale: LocaleRef,
|
||||
) -> crate::Result<()> {
|
||||
let mut message: ArrayString<BUFFER_LENGTH> = MESSAGE_HEADER.try_into().unwrap();
|
||||
let passwords: PasswordArray = spawn_blocking(generate_passwords).await?;
|
||||
for password in passwords {
|
||||
@ -23,7 +27,7 @@ pub async fn gen_password(bot: Throttle<Bot>, msg: Message) -> crate::Result<()>
|
||||
}
|
||||
bot.send_message(msg.chat.id, message.as_str())
|
||||
.parse_mode(ParseMode::MarkdownV2)
|
||||
.reply_markup(deletion_markup())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,19 +1,24 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[inline]
|
||||
pub async fn get_account(bot: Throttle<Bot>, msg: Message, db: Pool) -> crate::Result<()> {
|
||||
pub async fn get_account(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: Pool,
|
||||
locale: LocaleRef,
|
||||
) -> crate::Result<()> {
|
||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||
|
||||
let markup = menu_markup("decrypt", user_id, &db).await?;
|
||||
|
||||
if markup.inline_keyboard.is_empty() {
|
||||
bot.send_message(msg.chat.id, "You don't have any accounts")
|
||||
.reply_markup(deletion_markup())
|
||||
bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
bot.send_message(msg.chat.id, "Choose the account to get")
|
||||
bot.send_message(msg.chat.id, locale.choose_account.as_ref())
|
||||
.reply_markup(markup)
|
||||
.await?;
|
||||
Ok(())
|
||||
|
@ -4,26 +4,32 @@ use teloxide::types::ParseMode;
|
||||
|
||||
/// Handles /`get_accounts` command by sending the list of copyable account names to the user
|
||||
#[inline]
|
||||
pub async fn get_accounts(bot: Throttle<Bot>, msg: Message, db: Pool) -> crate::Result<()> {
|
||||
pub async fn get_accounts(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: Pool,
|
||||
locale: LocaleRef,
|
||||
) -> crate::Result<()> {
|
||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||
let mut account_names = Account::get_names(user_id, &db);
|
||||
|
||||
let mut text = if let Some(name) = account_names.try_next().await? {
|
||||
format!("Accounts:\n`{name}`")
|
||||
} else {
|
||||
bot.send_message(msg.chat.id, "No accounts found")
|
||||
.reply_markup(deletion_markup())
|
||||
let Some(mut text) = account_names.try_next().await? else {
|
||||
bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
text.insert(0, '`');
|
||||
text.push('`');
|
||||
|
||||
while let Some(name) = account_names.try_next().await? {
|
||||
write!(text, "\n`{name}`")?;
|
||||
}
|
||||
|
||||
bot.send_message(msg.chat.id, text)
|
||||
.parse_mode(ParseMode::MarkdownV2)
|
||||
.reply_markup(deletion_markup())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
use crate::prelude::*;
|
||||
use teloxide::utils::command::BotCommands;
|
||||
|
||||
/// Handles the help command by sending the passwords descryptions
|
||||
#[inline]
|
||||
pub async fn help(bot: Throttle<Bot>, msg: Message) -> crate::Result<()> {
|
||||
bot.send_message(msg.chat.id, Command::descriptions().to_string())
|
||||
.reply_markup(deletion_markup())
|
||||
pub async fn help(bot: Throttle<Bot>, msg: Message, locale: LocaleRef) -> crate::Result<()> {
|
||||
bot.send_message(msg.chat.id, locale.help_command.as_ref())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -27,12 +27,14 @@ async fn encrypt_account(
|
||||
|
||||
/// Gets the master password, encryptes and adds the accounts to the DB
|
||||
#[inline]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn get_master_pass(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: Pool,
|
||||
dialogue: MainDialogue,
|
||||
mut ids: MessageIds,
|
||||
locale: LocaleRef,
|
||||
user: User,
|
||||
master_pass: String,
|
||||
) -> crate::Result<()> {
|
||||
@ -53,22 +55,18 @@ async fn get_master_pass(
|
||||
}
|
||||
|
||||
let text = if failed.is_empty() {
|
||||
"Success".to_owned()
|
||||
locale.success.as_ref().to_owned()
|
||||
} else {
|
||||
format!(
|
||||
"Failed to create the following accounts:\n{}",
|
||||
"{}:\n{}",
|
||||
locale.couldnt_create_following_accounts,
|
||||
failed.into_iter().format("\n")
|
||||
)
|
||||
};
|
||||
ids.alter_message(&bot, text, deletion_markup(), None)
|
||||
ids.alter_message(&bot, text, deletion_markup(locale), None)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
handler!(get_user(user: User), "Send master password", State::GetMasterPass, get_master_pass);
|
||||
first_handler!(
|
||||
import,
|
||||
"Send a json document with the same format as created by /export",
|
||||
State::GetUser,
|
||||
get_user
|
||||
);
|
||||
handler!(get_user(user: User),send_master_password, State::GetMasterPass, get_master_pass);
|
||||
first_handler!(import, send_json_file, State::GetUser, get_user);
|
||||
|
@ -1,19 +1,24 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[inline]
|
||||
pub async fn menu(bot: Throttle<Bot>, msg: Message, db: Pool) -> crate::Result<()> {
|
||||
pub async fn menu(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: Pool,
|
||||
locale: LocaleRef,
|
||||
) -> crate::Result<()> {
|
||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||
|
||||
let markup = menu_markup("get", user_id, &db).await?;
|
||||
|
||||
if markup.inline_keyboard.is_empty() {
|
||||
bot.send_message(msg.chat.id, "You don't have any accounts")
|
||||
.reply_markup(deletion_markup())
|
||||
bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
bot.send_message(msg.chat.id, "Choose your account")
|
||||
bot.send_message(msg.chat.id, locale.choose_account.as_ref())
|
||||
.reply_markup(markup)
|
||||
.await?;
|
||||
Ok(())
|
||||
|
@ -1,25 +1,29 @@
|
||||
use crate::{change_state, prelude::*};
|
||||
use crate::{change_state, locales::LocaleTypeExt, prelude::*};
|
||||
use cryptography::hashing::HashedBytes;
|
||||
use entity::locale::LocaleType;
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn get_master_pass2(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: Pool,
|
||||
dialogue: MainDialogue,
|
||||
mut ids: MessageIds,
|
||||
locale: LocaleRef,
|
||||
hash: HashedBytes<[u8; 64], [u8; 64]>,
|
||||
master_pass: String,
|
||||
) -> crate::Result<()> {
|
||||
dialogue.exit().await?;
|
||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||
let from = msg.from().ok_or(NoUserInfo)?;
|
||||
let user_id = from.id.0;
|
||||
|
||||
if !hash.verify(master_pass.as_bytes()) {
|
||||
ids.alter_message(
|
||||
&bot,
|
||||
"The passwords didn't match. Use the command again",
|
||||
deletion_markup(),
|
||||
locale.master_password_dont_match.as_ref(),
|
||||
deletion_markup(locale),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
@ -32,9 +36,14 @@ async fn get_master_pass2(
|
||||
password_hash: hash.hash.to_vec(),
|
||||
salt: hash.salt.to_vec(),
|
||||
};
|
||||
model.insert(&db).await?;
|
||||
let locale_type = from
|
||||
.language_code
|
||||
.as_deref()
|
||||
.and_then(LocaleType::from_language_code)
|
||||
.unwrap_or_default();
|
||||
model.insert(&db, locale_type).await?;
|
||||
|
||||
ids.alter_message(&bot, "Success", deletion_markup(), None)
|
||||
ids.alter_message(&bot, locale.success.as_ref(), deletion_markup(locale), None)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
@ -47,11 +56,13 @@ async fn get_master_pass(
|
||||
_: Pool,
|
||||
dialogue: MainDialogue,
|
||||
mut ids: MessageIds,
|
||||
locale: LocaleRef,
|
||||
master_pass: String,
|
||||
) -> crate::Result<()> {
|
||||
let hash = spawn_blocking(move || HashedBytes::new(master_pass.as_bytes())).await?;
|
||||
|
||||
ids.alter_message(&bot, "Send it again", None, None).await?;
|
||||
ids.alter_message(&bot, locale.send_master_password_again.as_ref(), None, None)
|
||||
.await?;
|
||||
|
||||
change_state!(
|
||||
dialogue,
|
||||
@ -71,16 +82,17 @@ pub async fn set_master_pass(
|
||||
msg: Message,
|
||||
dialogue: MainDialogue,
|
||||
db: Pool,
|
||||
locale: LocaleRef,
|
||||
) -> crate::Result<()> {
|
||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||
if MasterPass::exists(user_id, &db).await? {
|
||||
bot.send_message(msg.chat.id, "Master password already exists")
|
||||
.reply_markup(deletion_markup())
|
||||
bot.send_message(msg.chat.id, locale.master_password_is_set.as_ref())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
let previous = bot
|
||||
.send_message(msg.chat.id, "Send new master password")
|
||||
.send_message(msg.chat.id, locale.send_new_master_password.as_ref())
|
||||
.await?;
|
||||
|
||||
change_state!(
|
||||
|
@ -2,12 +2,8 @@ use crate::prelude::*;
|
||||
|
||||
/// Handles /start command by sending the greeting message
|
||||
#[inline]
|
||||
pub async fn start(bot: Throttle<Bot>, msg: Message) -> crate::Result<()> {
|
||||
bot.send_message(
|
||||
msg.chat.id,
|
||||
"Hi! This bot can be used to store the passwords securely. \
|
||||
Use /help command to get the list of commands",
|
||||
)
|
||||
.await?;
|
||||
pub async fn start(bot: Throttle<Bot>, msg: Message, locale: LocaleRef) -> crate::Result<()> {
|
||||
bot.send_message(msg.chat.id, locale.start_command.as_ref())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2,12 +2,9 @@ use crate::prelude::*;
|
||||
|
||||
/// Handles the messages which weren't matched by any commands or states
|
||||
#[inline]
|
||||
pub async fn default(bot: Throttle<Bot>, msg: Message) -> crate::Result<()> {
|
||||
bot.send_message(
|
||||
msg.chat.id,
|
||||
"Unknown command. Use /help command to get the list of commands",
|
||||
)
|
||||
.reply_markup(deletion_markup())
|
||||
.await?;
|
||||
pub async fn default(bot: Throttle<Bot>, msg: Message, locale: LocaleRef) -> crate::Result<()> {
|
||||
bot.send_message(msg.chat.id, locale.unknown_command_use_help.as_ref())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -14,4 +14,6 @@ pub enum InvalidCommand {
|
||||
InvalidOutputLength,
|
||||
#[error("Error decoding the values: {0}")]
|
||||
NameDecodingError(#[from] base64::DecodeError),
|
||||
#[error("Unknown locale passed into callback")]
|
||||
UnknownLocale,
|
||||
}
|
||||
|
@ -13,13 +13,14 @@ async fn notify_about_no_user_info(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
state: State,
|
||||
locale: LocaleRef,
|
||||
) -> crate::Result<()> {
|
||||
const TEXT: &str = "Invalid message. Couldn't get the user information. Send the message again";
|
||||
let text = locale.couldnt_get_user_info_send_again.as_ref();
|
||||
|
||||
match state {
|
||||
State::Start => {
|
||||
bot.send_message(msg.chat.id, TEXT)
|
||||
.reply_markup(deletion_markup())
|
||||
bot.send_message(msg.chat.id, text)
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
}
|
||||
State::GetNewName(handler)
|
||||
@ -30,14 +31,14 @@ async fn notify_about_no_user_info(
|
||||
let mut handler = handler.lock().await;
|
||||
handler
|
||||
.previous
|
||||
.alter_message(&bot, TEXT, None, None)
|
||||
.alter_message(&bot, text, None, None)
|
||||
.await?;
|
||||
}
|
||||
State::GetUser(handler) => {
|
||||
let mut handler = handler.lock().await;
|
||||
handler
|
||||
.previous
|
||||
.alter_message(&bot, TEXT, None, None)
|
||||
.alter_message(&bot, text, None, None)
|
||||
.await?;
|
||||
}
|
||||
};
|
||||
|
166
src/locales.rs
Normal file
166
src/locales.rs
Normal file
@ -0,0 +1,166 @@
|
||||
use crate::prelude::*;
|
||||
use anyhow::Result;
|
||||
use entity::locale::LocaleType;
|
||||
use log::error;
|
||||
use std::future::Future;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static LOCALES: OnceLock<LocaleStore> = OnceLock::new();
|
||||
|
||||
pub struct LocaleStore {
|
||||
eng: Locale,
|
||||
ru: Locale,
|
||||
}
|
||||
|
||||
impl LocaleStore {
|
||||
pub fn init() -> Result<()> {
|
||||
let ru = serde_yaml::from_slice(include_bytes!("../locales/ru.yaml"))?;
|
||||
let eng = serde_yaml::from_slice(include_bytes!("../locales/eng.yaml"))?;
|
||||
|
||||
assert!(
|
||||
LOCALES.set(Self { eng, ru }).is_ok(),
|
||||
"Locales are already intialized"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct Locale {
|
||||
pub master_password_is_not_set: Box<str>,
|
||||
pub hide_button: Box<str>,
|
||||
pub change_name_button: Box<str>,
|
||||
pub change_login_button: Box<str>,
|
||||
pub change_password_button: Box<str>,
|
||||
pub delete_account_button: Box<str>,
|
||||
pub couldnt_get_user_info_send_again: Box<str>,
|
||||
pub unknown_command_use_help: Box<str>,
|
||||
pub help_command: Box<str>,
|
||||
pub no_file_send: Box<str>,
|
||||
pub file_too_large: Box<str>,
|
||||
pub couldnt_get_file_name: Box<str>,
|
||||
pub following_accounts_have_problems: Box<str>,
|
||||
pub duplicate_names: Box<str>,
|
||||
pub accounts_already_in_db: Box<str>,
|
||||
pub invalid_fields: Box<str>,
|
||||
pub fix_that_and_send_again: Box<str>,
|
||||
pub error_downloading_file: Box<str>,
|
||||
pub error_getting_account_names: Box<str>,
|
||||
pub error_parsing_json_file: Box<str>,
|
||||
pub successfully_canceled: Box<str>,
|
||||
pub invalid_password: Box<str>,
|
||||
pub couldnt_get_message_text: Box<str>,
|
||||
pub invalid_file_name: Box<str>,
|
||||
pub account_already_exists: Box<str>,
|
||||
pub master_password_too_weak: Box<str>,
|
||||
pub no_lowercase: Box<str>,
|
||||
pub no_uppercase: Box<str>,
|
||||
pub no_numbers: Box<str>,
|
||||
pub master_pass_too_short: Box<str>,
|
||||
pub change_master_password_and_send_again: Box<str>,
|
||||
pub wrong_master_password: Box<str>,
|
||||
pub invalid_login: Box<str>,
|
||||
pub start_command: Box<str>,
|
||||
pub master_password_dont_match: Box<str>,
|
||||
pub success: Box<str>,
|
||||
pub send_master_password_again: Box<str>,
|
||||
pub master_password_is_set: Box<str>,
|
||||
pub send_new_master_password: Box<str>,
|
||||
pub no_accounts_found: Box<str>,
|
||||
pub choose_account: Box<str>,
|
||||
pub couldnt_create_following_accounts: Box<str>,
|
||||
pub send_master_password: Box<str>,
|
||||
pub something_went_wrong: Box<str>,
|
||||
pub nothing_to_cancel: Box<str>,
|
||||
pub send_account_name: Box<str>,
|
||||
pub send_login: Box<str>,
|
||||
pub error_deleting_message: Box<str>,
|
||||
pub account_not_found: Box<str>,
|
||||
pub send_new_name: Box<str>,
|
||||
pub send_new_login: Box<str>,
|
||||
pub send_new_password: Box<str>,
|
||||
pub success_choose_account_to_view: Box<str>,
|
||||
pub send_json_file: Box<str>,
|
||||
pub send_master_pass_to_delete_everything: Box<str>,
|
||||
pub everything_was_deleted: Box<str>,
|
||||
pub send_password: Box<str>,
|
||||
pub send_master_pass_to_delete_account: Box<str>,
|
||||
pub no_special_characters: Box<str>,
|
||||
pub invalid_name: Box<str>,
|
||||
pub decrypt_button: Box<str>,
|
||||
pub delete_message_button: Box<str>,
|
||||
pub menu_button: Box<str>,
|
||||
pub choose_language: Box<str>,
|
||||
word_name: Box<str>,
|
||||
word_login: Box<str>,
|
||||
word_password: Box<str>,
|
||||
}
|
||||
|
||||
impl Locale {
|
||||
pub async fn from_update(update: Update, db: Pool) -> &'static Self {
|
||||
let locale_type = LocaleType::locale_for_update(&update, &db).await;
|
||||
locale_type.get_locale()
|
||||
}
|
||||
|
||||
pub fn show_account(&self, name: &str, login: &str, password: &str) -> String {
|
||||
format!(
|
||||
"{}:\n`{name}`\n{}:\n`{login}`\n{}:\n`{password}`",
|
||||
self.word_name, self.word_login, self.word_password
|
||||
)
|
||||
}
|
||||
|
||||
pub fn show_hidden_account(&self, name: &str) -> String {
|
||||
format!(
|
||||
"{}:\n`{name}`\n{}:\n\\*\\*\\*\n{}:\n\\*\\*\\*",
|
||||
self.word_name, self.word_login, self.word_password
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub type LocaleRef = &'static Locale;
|
||||
|
||||
pub trait LocaleTypeExt: Sized {
|
||||
fn locale_for_update(update: &Update, db: &Pool) -> impl Future<Output = Self> + Send;
|
||||
|
||||
fn from_language_code(code: &str) -> Option<Self>;
|
||||
|
||||
fn get_locale(self) -> &'static Locale;
|
||||
}
|
||||
|
||||
impl LocaleTypeExt for LocaleType {
|
||||
async fn locale_for_update(update: &Update, db: &Pool) -> Self {
|
||||
let Some(from) = update.user() else {
|
||||
return Self::default();
|
||||
};
|
||||
|
||||
match Self::get_from_db(from.id.0, db).await {
|
||||
Ok(Some(locale)) => return locale,
|
||||
Ok(None) => (),
|
||||
Err(err) => error!("{err}"),
|
||||
}
|
||||
|
||||
from.language_code
|
||||
.as_deref()
|
||||
.and_then(Self::from_language_code)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn from_language_code(code: &str) -> Option<Self> {
|
||||
match code {
|
||||
"ru" => Some(Self::Ru),
|
||||
"en" => Some(Self::Eng),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_locale(self) -> &'static Locale {
|
||||
let Some(store) = LOCALES.get() else {
|
||||
panic!("Locales are not initialized")
|
||||
};
|
||||
match self {
|
||||
Self::Eng => &store.eng,
|
||||
Self::Ru => &store.ru,
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ macro_rules! change_state {
|
||||
($dialogue: expr, $previous: expr, ($($param: ident),*), $next_state: expr, $next_func: ident) => {{
|
||||
$dialogue
|
||||
.update($next_state(Handler::new(
|
||||
move |bot, msg, db, dialogue, ids, param| Box::pin($next_func(bot, msg, db, dialogue, ids, $($param,)* param)),
|
||||
move |bot, msg, db, dialogue, locale, ids, param| Box::pin($next_func(bot, msg, db, dialogue, locale, ids, $($param,)* param)),
|
||||
$previous,
|
||||
)))
|
||||
.await?;
|
||||
@ -12,14 +12,17 @@ macro_rules! change_state {
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! first_handler {
|
||||
($function_name: ident, $message: expr, $next_state: expr, $next_func: ident) => {
|
||||
($function_name: ident, $message: ident, $next_state: expr, $next_func: ident) => {
|
||||
#[inline]
|
||||
pub async fn $function_name(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
dialogue: MainDialogue,
|
||||
locale: LocaleRef,
|
||||
) -> $crate::Result<()> {
|
||||
let previous = bot.send_message(msg.chat.id, $message).await?;
|
||||
let previous = bot
|
||||
.send_message(msg.chat.id, locale.$message.as_ref())
|
||||
.await?;
|
||||
|
||||
$crate::change_state!(dialogue, &previous, (), $next_state, $next_func);
|
||||
|
||||
@ -30,7 +33,7 @@ macro_rules! first_handler {
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! handler {
|
||||
($function_name: ident ($($param: ident: $type: ty),*), $message: literal, $next_state: expr, $next_func: ident) => {
|
||||
($function_name: ident ($($param: ident: $type: ty),*), $message: ident, $next_state: expr, $next_func: ident) => {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[inline]
|
||||
async fn $function_name(
|
||||
@ -39,9 +42,10 @@ macro_rules! handler {
|
||||
_: Pool,
|
||||
dialogue: MainDialogue,
|
||||
mut ids: MessageIds,
|
||||
locale: LocaleRef,
|
||||
$($param: $type),*
|
||||
) -> $crate::Result<()> {
|
||||
ids.alter_message(&bot, $message, None, None).await?;
|
||||
ids.alter_message(&bot, locale.$message.as_ref(), None, None).await?;
|
||||
|
||||
$crate::change_state!(dialogue, ids, ($($param),*), $next_state, $next_func);
|
||||
|
||||
@ -52,13 +56,14 @@ macro_rules! handler {
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! simple_state_handler {
|
||||
($function_name: ident, $check: ident, $no_text_message: literal) => {
|
||||
($function_name: ident, $check: ident) => {
|
||||
#[inline]
|
||||
pub async fn $function_name(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: Pool,
|
||||
dialogue: MainDialogue,
|
||||
locale: LocaleRef,
|
||||
next: PackagedHandler<String>,
|
||||
) -> $crate::Result<()> {
|
||||
super::generic::generic(
|
||||
@ -66,8 +71,8 @@ macro_rules! simple_state_handler {
|
||||
msg,
|
||||
db,
|
||||
dialogue,
|
||||
|msg, db, param| Box::pin($check(msg, db, param)),
|
||||
$no_text_message,
|
||||
|msg, db, locale, param| Box::pin($check(msg, db, locale, param)),
|
||||
locale,
|
||||
next,
|
||||
)
|
||||
.await
|
||||
|
10
src/main.rs
10
src/main.rs
@ -4,6 +4,7 @@ mod default;
|
||||
mod delete_mesage_handler;
|
||||
mod errors;
|
||||
mod filter_user_info;
|
||||
mod locales;
|
||||
mod macros;
|
||||
mod markups;
|
||||
mod master_password_check;
|
||||
@ -42,7 +43,8 @@ fn get_dispatcher(
|
||||
.branch(case![Command::Delete].endpoint(commands::delete))
|
||||
.branch(case![Command::DeleteAll].endpoint(commands::delete_all))
|
||||
.branch(case![Command::Export].endpoint(commands::export))
|
||||
.branch(case![Command::Import].endpoint(commands::import));
|
||||
.branch(case![Command::Import].endpoint(commands::import))
|
||||
.branch(case![Command::ChangeLanguage].endpoint(commands::change_language));
|
||||
|
||||
let message_handler = Update::filter_message()
|
||||
.map_async(delete_mesage_handler::delete_message)
|
||||
@ -67,9 +69,11 @@ fn get_dispatcher(
|
||||
.branch(
|
||||
case![CallbackCommand::DeleteAccount { name, is_command }].endpoint(callbacks::delete),
|
||||
)
|
||||
.branch(case![CallbackCommand::Alter(hash, field)].endpoint(callbacks::alter));
|
||||
.branch(case![CallbackCommand::Alter(hash, field)].endpoint(callbacks::alter))
|
||||
.branch(case![CallbackCommand::ChangeLocale(locale)].endpoint(callbacks::change_locale));
|
||||
|
||||
let handler = dptree::entry()
|
||||
.map_async(Locale::from_update)
|
||||
.enter_dialogue::<Update, InMemStorage<State>, State>()
|
||||
.branch(message_handler)
|
||||
.branch(callback_handler);
|
||||
@ -85,6 +89,8 @@ async fn main() -> Result<()> {
|
||||
let _ = dotenv();
|
||||
pretty_env_logger::init();
|
||||
|
||||
locales::LocaleStore::init()?;
|
||||
|
||||
let token = env::var("TOKEN").expect("expected TOKEN in the enviroment");
|
||||
let database_url = env::var("DATABASE_URL").expect("expected DATABASE_URL in the enviroment");
|
||||
let pool = Pool::connect(&database_url).await?;
|
||||
|
@ -40,16 +40,16 @@ pub async fn menu_markup(
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn make_button(text: &str, command: &str, hash: &str) -> InlineKeyboardButton {
|
||||
fn make_button(text: &str, command: &str, param: &str) -> InlineKeyboardButton {
|
||||
let mut data = command.to_owned();
|
||||
data.reserve(44);
|
||||
data.reserve(param.len() + 1);
|
||||
data.push(' ');
|
||||
data.push_str(hash);
|
||||
data.push_str(param);
|
||||
InlineKeyboardButton::callback(text, data)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn account_markup(name: &str, is_encrypted: bool) -> InlineKeyboardMarkup {
|
||||
pub fn account_markup(name: &str, is_encrypted: bool, locale: LocaleRef) -> InlineKeyboardMarkup {
|
||||
let mut hash = [0; 43];
|
||||
B64_ENGINE
|
||||
.encode_slice(<Sha256 as Digest>::digest(name), &mut hash)
|
||||
@ -57,31 +57,41 @@ pub fn account_markup(name: &str, is_encrypted: bool) -> InlineKeyboardMarkup {
|
||||
let hash = std::str::from_utf8(&hash).unwrap();
|
||||
|
||||
let encryption_button = if is_encrypted {
|
||||
("Decrypt", "decrypt")
|
||||
(locale.decrypt_button.as_ref(), "decrypt")
|
||||
} else {
|
||||
("Hide", "get")
|
||||
(locale.hide_button.as_ref(), "get")
|
||||
};
|
||||
|
||||
let main_buttons = [
|
||||
("Alter name", "an"),
|
||||
("Alter login", "al"),
|
||||
("Alter password", "ap"),
|
||||
(locale.change_name_button.as_ref(), "an"),
|
||||
(locale.change_login_button.as_ref(), "al"),
|
||||
(locale.change_password_button.as_ref(), "ap"),
|
||||
encryption_button,
|
||||
("Delete account", "delete0"),
|
||||
(locale.delete_account_button.as_ref(), "delete0"),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(text, command)| make_button(text, command, hash))
|
||||
.chunks(3);
|
||||
|
||||
let menu_button = InlineKeyboardButton::callback("Back to the menu", "get_menu");
|
||||
let menu_button = InlineKeyboardButton::callback(locale.menu_button.as_ref(), "get_menu");
|
||||
|
||||
InlineKeyboardMarkup::new(&main_buttons).append_row([menu_button])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn language_markup() -> InlineKeyboardMarkup {
|
||||
let languages = [("🇷🇺 Русский", "ru"), ("🇬🇧 English", "en")]
|
||||
.into_iter()
|
||||
.map(|(text, param)| make_button(text, "change_locale", param))
|
||||
.chunks(3);
|
||||
InlineKeyboardMarkup::new(&languages)
|
||||
}
|
||||
|
||||
/// Creates a markup with a "Delete message" button.
|
||||
/// This markup should be added for all messages that won't be deleted afterwards
|
||||
#[inline]
|
||||
pub fn deletion_markup() -> InlineKeyboardMarkup {
|
||||
let button = InlineKeyboardButton::callback("Delete message", "delete_message");
|
||||
pub fn deletion_markup(locale: LocaleRef) -> InlineKeyboardMarkup {
|
||||
let button =
|
||||
InlineKeyboardButton::callback(locale.delete_message_button.as_ref(), "delete_message");
|
||||
InlineKeyboardMarkup::new([[button]])
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ async fn master_pass_exists(update: Update, db: Pool) -> Option<Option<DynError>
|
||||
#[inline]
|
||||
async fn notify_about_no_master_pass(
|
||||
bot: Throttle<Bot>,
|
||||
locale: LocaleRef,
|
||||
result: Option<DynError>,
|
||||
update: Update,
|
||||
) -> crate::Result<()> {
|
||||
@ -37,9 +38,9 @@ async fn notify_about_no_master_pass(
|
||||
}
|
||||
bot.send_message(
|
||||
update.chat_id().unwrap(),
|
||||
"No master password set. Use /set_master_pass to set it",
|
||||
locale.master_password_is_not_set.as_ref(),
|
||||
)
|
||||
.reply_markup(deletion_markup())
|
||||
.reply_markup(deletion_markup(locale))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ pub use crate::{
|
||||
commands::Command,
|
||||
errors::*,
|
||||
first_handler, handler,
|
||||
locales::{Locale, LocaleRef},
|
||||
markups::*,
|
||||
models::*,
|
||||
state::{Handler, MainDialogue, MessageIds, PackagedHandler, State},
|
||||
|
@ -9,11 +9,16 @@ pub async fn generic<F>(
|
||||
db: Pool,
|
||||
dialogue: MainDialogue,
|
||||
check: F,
|
||||
no_text_message: impl Into<String> + Send,
|
||||
locale: LocaleRef,
|
||||
handler: PackagedHandler<String>,
|
||||
) -> crate::Result<()>
|
||||
where
|
||||
for<'a> F: FnOnce(&'a Message, &'a Pool, &'a str) -> BoxFuture<'a, crate::Result<Option<String>>>
|
||||
for<'a> F: FnOnce(
|
||||
&'a Message,
|
||||
&'a Pool,
|
||||
LocaleRef,
|
||||
&'a str,
|
||||
) -> BoxFuture<'a, crate::Result<Option<String>>>
|
||||
+ Send,
|
||||
{
|
||||
let mut handler = handler.lock().await;
|
||||
@ -25,7 +30,7 @@ where
|
||||
let Some(text) = msg.text().map(str::trim) else {
|
||||
handler
|
||||
.previous
|
||||
.alter_message(&bot, no_text_message, None, None)
|
||||
.alter_message(&bot, locale.couldnt_get_message_text.as_ref(), None, None)
|
||||
.await?;
|
||||
return Ok(());
|
||||
};
|
||||
@ -34,12 +39,17 @@ where
|
||||
dialogue.exit().await?;
|
||||
handler
|
||||
.previous
|
||||
.alter_message(&bot, "Successfully cancelled", deletion_markup(), None)
|
||||
.alter_message(
|
||||
&bot,
|
||||
locale.successfully_canceled.as_ref(),
|
||||
deletion_markup(locale),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(text) = check(&msg, &db, text).await? {
|
||||
if let Some(text) = check(&msg, &db, locale, text).await? {
|
||||
handler
|
||||
.previous
|
||||
.alter_message(&bot, text, None, None)
|
||||
@ -52,7 +62,7 @@ where
|
||||
drop(handler);
|
||||
let text = text.to_owned();
|
||||
|
||||
if let Err(err) = func(bot, msg, db, dialogue.clone(), previous, text).await {
|
||||
if let Err(err) = func(bot, msg, db, dialogue.clone(), previous, locale, text).await {
|
||||
let _ = dialogue.exit().await;
|
||||
return Err(err);
|
||||
}
|
||||
|
@ -1,16 +1,17 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[inline]
|
||||
async fn check_login(_: &Message, _: &Pool, login: &str) -> crate::Result<Option<String>> {
|
||||
async fn check_login(
|
||||
_: &Message,
|
||||
_: &Pool,
|
||||
locale: LocaleRef,
|
||||
login: &str,
|
||||
) -> crate::Result<Option<String>> {
|
||||
let is_valid = validate_field(login);
|
||||
if !is_valid {
|
||||
return Ok(Some("Invalid login. Try again".to_owned()));
|
||||
return Ok(Some(locale.invalid_login.as_ref().into()));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
crate::simple_state_handler!(
|
||||
get_login,
|
||||
check_login,
|
||||
"Couldn't get the text of the message. Send the login again"
|
||||
);
|
||||
crate::simple_state_handler!(get_login, check_login);
|
||||
|
@ -8,14 +8,13 @@ use tokio::task::spawn_blocking;
|
||||
async fn check_master_pass(
|
||||
msg: &Message,
|
||||
db: &Pool,
|
||||
locale: LocaleRef,
|
||||
master_pass: &str,
|
||||
) -> crate::Result<Option<String>> {
|
||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||
let Some(model) = MasterPass::get(user_id, db).await? else {
|
||||
error!("User was put into the GetMasterPass state with no master password set");
|
||||
return Ok(Some(
|
||||
"No master password set. Use /cancel and set it by using /set_master_pass".to_owned(),
|
||||
));
|
||||
return Ok(Some(locale.master_password_is_not_set.as_ref().into()));
|
||||
};
|
||||
|
||||
let is_valid = {
|
||||
@ -25,13 +24,9 @@ async fn check_master_pass(
|
||||
};
|
||||
|
||||
if !is_valid {
|
||||
return Ok(Some("Wrong master password. Try again".to_owned()));
|
||||
return Ok(Some(locale.wrong_master_password.as_ref().into()));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
crate::simple_state_handler!(
|
||||
get_master_pass,
|
||||
check_master_pass,
|
||||
"Couldn't get the text of the message. Send the master password again"
|
||||
);
|
||||
crate::simple_state_handler!(get_master_pass, check_master_pass);
|
||||
|
@ -1,31 +1,32 @@
|
||||
use crate::prelude::*;
|
||||
use cryptography::passwords::{check_master_pass, PasswordValidity};
|
||||
use std::fmt::Write as _;
|
||||
|
||||
#[inline]
|
||||
fn process_validity(validity: PasswordValidity) -> Result<(), String> {
|
||||
fn process_validity(validity: PasswordValidity, locale: LocaleRef) -> Result<(), String> {
|
||||
if validity.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut error_text = "Your master password is invalid:\n".to_owned();
|
||||
let mut error_text = locale.master_password_too_weak.as_ref().to_owned();
|
||||
|
||||
if validity.contains(PasswordValidity::NO_LOWERCASE) {
|
||||
error_text.push_str("\n* It doesn't have any lowercase characters");
|
||||
write!(error_text, "\n* {}", locale.no_lowercase).unwrap();
|
||||
}
|
||||
if validity.contains(PasswordValidity::NO_UPPERCASE) {
|
||||
error_text.push_str("\n* It doesn't have any uppercase characters");
|
||||
write!(error_text, "\n* {}", locale.no_uppercase).unwrap();
|
||||
}
|
||||
if validity.contains(PasswordValidity::NO_NUMBER) {
|
||||
error_text.push_str("\n* It doesn't have any numbers");
|
||||
write!(error_text, "\n* {}", locale.no_numbers).unwrap();
|
||||
}
|
||||
if validity.contains(PasswordValidity::NO_SPECIAL_CHARACTER) {
|
||||
error_text.push_str("\n* It doesn't have any special characters");
|
||||
write!(error_text, "\n* {}", locale.no_special_characters).unwrap();
|
||||
}
|
||||
if validity.contains(PasswordValidity::TOO_SHORT) {
|
||||
error_text.push_str("\n* It is shorter than 8 characters");
|
||||
write!(error_text, "\n* {}", locale.master_pass_too_short).unwrap();
|
||||
}
|
||||
|
||||
error_text.push_str("\n\nModify your password and send it again");
|
||||
error_text.push_str(&locale.change_master_password_and_send_again);
|
||||
|
||||
Err(error_text)
|
||||
}
|
||||
@ -35,15 +36,12 @@ fn process_validity(validity: PasswordValidity) -> Result<(), String> {
|
||||
async fn check_new_master_pass(
|
||||
_: &Message,
|
||||
_: &Pool,
|
||||
locale: LocaleRef,
|
||||
password: &str,
|
||||
) -> crate::Result<Option<String>> {
|
||||
let validity = check_master_pass(password);
|
||||
|
||||
Ok(process_validity(validity).err())
|
||||
Ok(process_validity(validity, locale).err())
|
||||
}
|
||||
|
||||
crate::simple_state_handler!(
|
||||
get_new_master_pass,
|
||||
check_new_master_pass,
|
||||
"Couldn't get the text of the message. Send the master password again"
|
||||
);
|
||||
crate::simple_state_handler!(get_new_master_pass, check_new_master_pass);
|
||||
|
@ -5,21 +5,18 @@ use crate::prelude::*;
|
||||
async fn check_new_account_name(
|
||||
msg: &Message,
|
||||
db: &Pool,
|
||||
locale: LocaleRef,
|
||||
name: &str,
|
||||
) -> crate::Result<Option<String>> {
|
||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||
|
||||
if Account::exists(user_id, name, db).await? {
|
||||
Ok(Some("Account already exists".to_owned()))
|
||||
Ok(Some(locale.account_already_exists.as_ref().into()))
|
||||
} else if !validate_field(name) {
|
||||
Ok(Some("Invalid account name. Try again".to_owned()))
|
||||
Ok(Some(locale.invalid_name.as_ref().into()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
crate::simple_state_handler!(
|
||||
get_new_name,
|
||||
check_new_account_name,
|
||||
"Couldn't get the text of the message. Send the name of the new account again"
|
||||
);
|
||||
crate::simple_state_handler!(get_new_name, check_new_account_name);
|
||||
|
@ -1,16 +1,17 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[inline]
|
||||
async fn check_password(_: &Message, _: &Pool, password: &str) -> crate::Result<Option<String>> {
|
||||
async fn check_password(
|
||||
_: &Message,
|
||||
_: &Pool,
|
||||
locale: LocaleRef,
|
||||
password: &str,
|
||||
) -> crate::Result<Option<String>> {
|
||||
let is_valid = validate_field(password);
|
||||
if !is_valid {
|
||||
return Ok(Some("Invalid password. Try again".to_owned()));
|
||||
return Ok(Some(locale.invalid_password.as_ref().into()));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
crate::simple_state_handler!(
|
||||
get_password,
|
||||
check_password,
|
||||
"Couldn't get the text of the message. Send the password again"
|
||||
);
|
||||
crate::simple_state_handler!(get_password, check_password);
|
||||
|
@ -9,24 +9,43 @@ use teloxide::{
|
||||
use tokio::{task::spawn_blocking, try_join};
|
||||
use trim_in_place::TrimInPlace;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum InvalidDocument {
|
||||
NoFileSend,
|
||||
FileTooLarge,
|
||||
CouldntGetFileName,
|
||||
InvalidFileName,
|
||||
}
|
||||
|
||||
impl InvalidDocument {
|
||||
const fn into_str(self, locale: LocaleRef) -> &'static str {
|
||||
match self {
|
||||
Self::NoFileSend => &locale.no_file_send,
|
||||
Self::FileTooLarge => &locale.file_too_large,
|
||||
Self::CouldntGetFileName => &locale.couldnt_get_file_name,
|
||||
Self::InvalidFileName => &locale.invalid_file_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn validate_document(document: Option<&Document>) -> Result<&Document, &'static str> {
|
||||
fn validate_document(document: Option<&Document>) -> Result<&Document, InvalidDocument> {
|
||||
let Some(document) = document else {
|
||||
return Err("You didn't send a file. Try again");
|
||||
return Err(InvalidDocument::NoFileSend);
|
||||
};
|
||||
|
||||
if document.file.size > 1024 * 1024 * 200 {
|
||||
return Err("The file is larger than 200 MiB. Try splitting it into multiple files");
|
||||
return Err(InvalidDocument::FileTooLarge);
|
||||
}
|
||||
|
||||
let name = match document.file_name.as_deref() {
|
||||
Some(name) => Path::new(name.trim_end()),
|
||||
None => return Err("Couldn't get the name of the file. Try sending it again"),
|
||||
None => return Err(InvalidDocument::CouldntGetFileName),
|
||||
};
|
||||
|
||||
match name.extension() {
|
||||
Some(ext) if ext.eq_ignore_ascii_case("json") => Ok(document),
|
||||
_ => Err("Invalid file name. You need to send a json file. Try again"),
|
||||
_ => Err(InvalidDocument::InvalidFileName),
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +64,7 @@ async fn download_file(bot: &Throttle<Bot>, file: &FileMeta) -> crate::Result<Bo
|
||||
fn process_accounts(
|
||||
accounts: &mut [DecryptedAccount],
|
||||
existing_names: ahash::HashSet<String>,
|
||||
locale: LocaleRef,
|
||||
) -> crate::Result<Result<(), String>> {
|
||||
for account in accounts.iter_mut() {
|
||||
account.name.trim_in_place();
|
||||
@ -82,12 +102,13 @@ fn process_accounts(
|
||||
return Ok(Ok(()));
|
||||
}
|
||||
|
||||
let mut error_text = "Your accounts have the following problems:".to_owned();
|
||||
let mut error_text = locale.following_accounts_have_problems.as_ref().to_owned();
|
||||
|
||||
if !duplicates.is_empty() {
|
||||
write!(
|
||||
error_text,
|
||||
"\n\nDuplicate names:\n{:?}",
|
||||
"\n\n{}:\n{:?}",
|
||||
locale.duplicate_names,
|
||||
duplicates.into_iter().format("\n")
|
||||
)?;
|
||||
}
|
||||
@ -95,7 +116,8 @@ fn process_accounts(
|
||||
if !existing.is_empty() {
|
||||
write!(
|
||||
error_text,
|
||||
"\n\nAccounts with these names already exist in the database:\n{:?}",
|
||||
"\n\n{}:\n{:?}",
|
||||
locale.accounts_already_in_db,
|
||||
existing.into_iter().format("\n")
|
||||
)?;
|
||||
}
|
||||
@ -103,12 +125,13 @@ fn process_accounts(
|
||||
if !invalid.is_empty() {
|
||||
write!(
|
||||
error_text,
|
||||
"\n\nInvalid account fields:\n{:?}",
|
||||
"\n\n{}:\n{:?}",
|
||||
locale.invalid_fields,
|
||||
invalid.into_iter().format("\n")
|
||||
)?;
|
||||
}
|
||||
|
||||
error_text.push_str("\n\nFix these problems and send the file again");
|
||||
error_text.push_str(&locale.fix_that_and_send_again);
|
||||
|
||||
Ok(Err(error_text))
|
||||
}
|
||||
@ -117,10 +140,11 @@ fn process_accounts(
|
||||
fn user_from_bytes(
|
||||
bytes: impl AsRef<[u8]>,
|
||||
existing_names: ahash::HashSet<String>,
|
||||
locale: LocaleRef,
|
||||
) -> crate::Result<Result<User, String>> {
|
||||
let mut user: User = serde_json::from_slice(bytes.as_ref())?;
|
||||
drop(bytes);
|
||||
match process_accounts(&mut user.accounts, existing_names)? {
|
||||
match process_accounts(&mut user.accounts, existing_names, locale)? {
|
||||
Ok(()) => Ok(Ok(user)),
|
||||
Err(error_text) => Ok(Err(error_text)),
|
||||
}
|
||||
@ -132,21 +156,24 @@ async fn user_from_document(
|
||||
db: &Pool,
|
||||
document: Option<&Document>,
|
||||
user_id: u64,
|
||||
locale: LocaleRef,
|
||||
) -> Result<User, Cow<'static, str>> {
|
||||
let (data, existing_names) = {
|
||||
let file = &validate_document(document)?.file;
|
||||
let data = download_file(bot, file).map_err(|_| "Error downloading the file. Try again");
|
||||
let file = &validate_document(document)
|
||||
.map_err(|err| err.into_str(locale))?
|
||||
.file;
|
||||
let data = download_file(bot, file).map_err(|_| locale.error_downloading_file.as_ref());
|
||||
|
||||
let existing_names = Account::get_names(user_id, db)
|
||||
.try_collect()
|
||||
.map_err(|_| "Error getting existing account names. Try again");
|
||||
.map_err(|_| locale.error_getting_account_names.as_ref());
|
||||
|
||||
try_join!(data, existing_names)?
|
||||
};
|
||||
|
||||
match spawn_blocking(|| user_from_bytes(data, existing_names)).await {
|
||||
match spawn_blocking(|| user_from_bytes(data, existing_names, locale)).await {
|
||||
Ok(Ok(user)) => user.map_err(Cow::Owned),
|
||||
_ => Err(Cow::Borrowed("Error parsing the json file. Try again")),
|
||||
_ => Err(Cow::Borrowed(&locale.error_parsing_json_file)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,6 +184,7 @@ pub async fn get_user(
|
||||
msg: Message,
|
||||
db: Pool,
|
||||
dialogue: MainDialogue,
|
||||
locale: LocaleRef,
|
||||
handler: PackagedHandler<User>,
|
||||
) -> crate::Result<()> {
|
||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||
@ -171,12 +199,17 @@ pub async fn get_user(
|
||||
dialogue.exit().await?;
|
||||
handler
|
||||
.previous
|
||||
.alter_message(&bot, "Successfully cancelled", deletion_markup(), None)
|
||||
.alter_message(
|
||||
&bot,
|
||||
locale.successfully_canceled.as_ref(),
|
||||
deletion_markup(locale),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let user = match user_from_document(&bot, &db, msg.document(), user_id).await {
|
||||
let user = match user_from_document(&bot, &db, msg.document(), user_id, locale).await {
|
||||
Ok(user) => user,
|
||||
Err(error_text) => {
|
||||
handler
|
||||
@ -191,7 +224,7 @@ pub async fn get_user(
|
||||
let func = handler.func.take().unwrap();
|
||||
drop(handler);
|
||||
|
||||
if let Err(err) = func(bot, msg, db, dialogue.clone(), previous, user).await {
|
||||
if let Err(err) = func(bot, msg, db, dialogue.clone(), previous, locale, user).await {
|
||||
let _ = dialogue.exit().await;
|
||||
return Err(err);
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ type DynHanlder<T> = Box<
|
||||
Pool,
|
||||
MainDialogue,
|
||||
MessageIds,
|
||||
LocaleRef,
|
||||
T,
|
||||
) -> BoxFuture<'static, crate::Result<()>>
|
||||
+ Send,
|
||||
@ -92,6 +93,7 @@ impl<T> Handler<T> {
|
||||
Pool,
|
||||
MainDialogue,
|
||||
MessageIds,
|
||||
LocaleRef,
|
||||
T,
|
||||
) -> BoxFuture<'static, crate::Result<()>>
|
||||
+ Send
|
||||
|
Reference in New Issue
Block a user