Added support for multiple languages

This commit is contained in:
2024-04-16 16:02:48 +03:00
parent aded63f9d5
commit c5855fced7
52 changed files with 973 additions and 409 deletions

View File

@@ -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
);

View File

@@ -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(())
}

View 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(())
}

View File

@@ -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(())

View File

@@ -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
);

View File

@@ -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
);

View File

@@ -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(())
}

View File

@@ -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(())

View File

@@ -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(())
}

View File

@@ -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(())
}

View File

@@ -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);

View File

@@ -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(())

View File

@@ -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!(

View File

@@ -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(())
}