From 7efba1d2cfbc9d7f14b1b98a84725c39b63de035 Mon Sep 17 00:00:00 2001 From: Leonid Kozarin Date: Mon, 15 Jan 2024 01:49:22 +0300 Subject: [PATCH] Don't force a non-registered user to invoke command twice --- locales/en.yml | 2 +- locales/ru.yml | 2 +- src/handlers/options/consent.rs | 65 ++++++++++++++++++++++++++------ src/handlers/options/location.rs | 35 +++++++++-------- src/handlers/options/mod.rs | 9 +++-- 5 files changed, 80 insertions(+), 33 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 5c1fb9f..9bb59bf 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -17,7 +17,7 @@ registration: button: "Accept" consent: ok: "Your account has been created successfully!" - appendix: "Accepted by the %{username}\n\nThank you! Now you can invoke the command again." + appendix: "Accepted by the %{username}\n\nThank you!" set-option: language: success: "👌🏼👍🏼" diff --git a/locales/ru.yml b/locales/ru.yml index e756d57..6176d9d 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -17,7 +17,7 @@ registration: button: "Принять" consent: ok: "Аккаунт успешно создан!" - appendix: "Акцептировано %{username}\n\nБлагодарю за понимание! Теперь можно вызвать команду снова." + appendix: "Акцептировано %{username}\n\nБлагодарю за понимание!" set-option: language: success: "👌🏼👍🏼" diff --git a/src/handlers/options/consent.rs b/src/handlers/options/consent.rs index 2caee58..0c70276 100644 --- a/src/handlers/options/consent.rs +++ b/src/handlers/options/consent.rs @@ -1,36 +1,39 @@ +use std::str::FromStr; +use std::sync::Arc; +use derive_more::{Constructor, Display}; use rust_i18n::t; use teloxide::Bot; +use teloxide::dispatching::dialogue::{GetChatId, InMemStorage}; use teloxide::payloads::{AnswerCallbackQuerySetters, EditMessageTextSetters}; use teloxide::prelude::{CallbackQuery, UserId}; use teloxide::requests::Requester; use teloxide::types::ParseMode::Html; +use thiserror::Error; use crate::eula; use crate::handlers::HandlerResult; use crate::handlers::options::build_agreement_text; use crate::handlers::options::callback::{CallbackHandlerDIParams, CallbackPreprocessorResult, preprocess_callback, UserIdAware}; +use crate::handlers::options::location::{LocationDialogue, LocationState, send_location_request}; use crate::users::{Consent, UserService, UserServiceClient, UserServiceClientGrpc}; use crate::utils::get_full_name; -struct ConsentCallbackData { +#[derive(Display, Constructor)] +#[display("consent:{uid}:{lang_code}:{command}")] +pub struct ConsentCallbackData { uid: UserId, lang_code: String, + command: SavedSetCommand, } -impl std::fmt::Display for ConsentCallbackData { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("consent:{}:{}", self.uid, self.lang_code)) - } -} - -#[derive(Debug, derive_more::Display, thiserror::Error)] -struct InvalidConsentCallbackData(String); +#[derive(Debug, Display, Error)] +pub struct InvalidConsentCallbackData(String); impl TryFrom for ConsentCallbackData { type Error = InvalidConsentCallbackData; fn try_from(value: String) -> Result { - let parts: Vec<&str> = value.split(':').collect(); - if parts.len() != 3 || parts[0] != "consent" { + let parts: Vec<&str> = value.splitn(4, ':').collect(); + if parts.len() != 4 || parts[0] != "consent" { return Err(InvalidConsentCallbackData(value)) } let uid = parts[1].parse() @@ -38,10 +41,32 @@ impl TryFrom for ConsentCallbackData { Ok(Self { uid: UserId(uid), lang_code: parts[2].to_owned(), + command: SavedSetCommand::from_str(parts[3]) + .map_err(|_| InvalidConsentCallbackData(value))? }) } } +#[derive(Display)] +pub enum SavedSetCommand { + #[display("loc")] + Location, + #[display("lang:{_0}")] + Language(String) +} + +impl FromStr for SavedSetCommand { + type Err = (); + + fn from_str(s: &str) -> Result { + match s.split_once(':') { + Some((param, value)) if param == "lang" => Ok(Self::Language(value.to_owned())), + None if s == "loc" => Ok(Self::Location), + _ => Err(()) + } + } +} + impl UserIdAware for ConsentCallbackData { fn user_id(&self) -> UserId { self.uid @@ -54,7 +79,9 @@ pub fn callback_filter(query: CallbackQuery) -> bool { .is_some() } -pub async fn callback_handler(bot: Bot, query: CallbackQuery, usr_client: UserService) -> HandlerResult { +pub async fn callback_handler(bot: Bot, query: CallbackQuery, usr_client: UserService, + dialogue_storage: Arc>) -> HandlerResult { + let maybe_chat_id = query.chat_id(); let data = ConsentCallbackData::try_from(query.data.clone().ok_or("no data")?)?; let ctx = match preprocess_callback(CallbackHandlerDIParams::new(&bot, &query, usr_client), &data).await? { CallbackPreprocessorResult::Processed(context) => context, @@ -80,5 +107,19 @@ pub async fn callback_handler(bot: Bot, query: CallbackQuery, usr_client: UserSe _ => ctx.answer.show_alert(true) .text(t!("error.old-message", locale = &ctx.lang_code)) }.await?; + + let chat_id = maybe_chat_id.ok_or("no chat_id")?; + match data.command { + SavedSetCommand::Language(code) => { + ctx.usr_client.set_language(query.from.id, &code).await?; + let text = t!("set-option.language.success", locale = &code); + bot.send_message(chat_id, text).await?; + } + SavedSetCommand::Location => { + let dialogue = LocationDialogue::new(dialogue_storage, chat_id); + send_location_request(bot, chat_id, dialogue, &ctx.lang_code).await?; + } + }; + Ok(()) } diff --git a/src/handlers/options/location.rs b/src/handlers/options/location.rs index 1e62e84..d6c7c6a 100644 --- a/src/handlers/options/location.rs +++ b/src/handlers/options/location.rs @@ -6,10 +6,11 @@ use teloxide::macros::BotCommands; use teloxide::payloads::{SendMessageSetters}; use teloxide::prelude::Dialogue; use teloxide::requests::Requester; -use teloxide::types::{ButtonRequest, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, KeyboardMarkup, KeyboardRemove, Message, ReplyMarkup, User}; +use teloxide::types::{ButtonRequest, ChatId, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, KeyboardMarkup, KeyboardRemove, Message, ReplyMarkup, User}; use teloxide::types::ParseMode::Html; use crate::handlers::{AnswerMessage, HandlerResult, process_answer_message}; use crate::handlers::options::callback::CancellationCallbackData; +use crate::handlers::options::consent::SavedSetCommand; use crate::handlers::options::register_user; use crate::metrics; use crate::users::{UserService, UserServiceClient, UserServiceClientGrpc}; @@ -46,19 +47,7 @@ pub async fn start(bot: Bot, dialogue: LocationDialogue, msg: Message, usr_clien MaybeContext::DialogueContext { lang_code, .. } => lang_code, MaybeContext::MessageToSend(answer) => return process_answer_message(bot, msg.chat.id, answer).await }; - - let msg_text = t!("set-option.location.message.text", locale = &lang_code); - let btn_text = t!("set-option.location.message.button", locale = &lang_code); - let keyboard = KeyboardMarkup::new(vec![vec![ - KeyboardButton::new(btn_text).request(ButtonRequest::Location) - ]]); - - bot.send_message(msg.chat.id, msg_text) - .parse_mode(Html) - .reply_markup(keyboard) - .await?; - - dialogue.update(LocationState::Requested).await?; + send_location_request(bot, msg.chat.id, dialogue, &lang_code).await?; Ok(()) } @@ -97,6 +86,22 @@ pub async fn requested(bot: Bot, msg: Message, dialogue: LocationDialogue, usr_c Ok(()) } +pub(super) async fn send_location_request(bot: Bot, chat_id: ChatId, dialogue: LocationDialogue, lang_code: &str) -> HandlerResult { + let msg_text = t!("set-option.location.message.text", locale = lang_code); + let btn_text = t!("set-option.location.message.button", locale = lang_code); + let keyboard = KeyboardMarkup::new(vec![vec![ + KeyboardButton::new(btn_text).request(ButtonRequest::Location) + ]]); + + bot.send_message(chat_id, msg_text) + .parse_mode(Html) + .reply_markup(keyboard) + .await?; + + dialogue.update(LocationState::Requested).await?; + Ok(()) +} + async fn build_context(user: &User, usr_client: UserService) -> anyhow::Result> { use MaybeContext::*; @@ -104,7 +109,7 @@ async fn build_context(user: &User, usr_client: UserServ let res = match usr_client { UserService::Connected(client) => { if client.get(user.id).await?.is_none() { - MessageToSend(register_user(client, user).await?) + MessageToSend(register_user(client, user, SavedSetCommand::Location).await?) } else { DialogueContext { usr_client: client, lang_code } } diff --git a/src/handlers/options/mod.rs b/src/handlers/options/mod.rs index 0cf8e05..5245b2f 100644 --- a/src/handlers/options/mod.rs +++ b/src/handlers/options/mod.rs @@ -12,6 +12,7 @@ use crate::users::UserServiceClient; use crate::utils::ensure_lang_code; pub use callback::{cancellation_filter, cancellation_handler, CancellationCallbackData}; +use crate::handlers::options::consent::{ConsentCallbackData, SavedSetCommand}; #[derive(Debug, strum_macros::Display, Clone)] #[strum(serialize_all="lowercase")] @@ -53,19 +54,19 @@ pub(super) async fn cmd_set_language_handler(usr_client: impl UserServiceClient, usr_client.set_language(user.id, &code).await?; Ok(t!("set-option.language.success", locale = &code).into()) }, - None => register_user(usr_client, user).await + None => register_user(usr_client, user, SavedSetCommand::Language(code)).await } } -async fn register_user(client: impl UserServiceClient, user: &teloxide::types::User) -> anyhow::Result { +async fn register_user(client: impl UserServiceClient, user: &teloxide::types::User, cmd: SavedSetCommand) -> anyhow::Result { let lang_code = &ensure_lang_code(user.id, user.language_code.clone(), &client.into()).await; let msg_text = build_agreement_text(lang_code); let btn_text = t!("registration.message.button", locale = lang_code); - let btn_data = format!("consent:{}:{lang_code}", user.id); + let btn_data = ConsentCallbackData::new(user.id, lang_code.to_owned(), cmd); let keyboard = InlineKeyboardMarkup::new(vec![vec![ - InlineKeyboardButton::callback(btn_text, btn_data) + InlineKeyboardButton::callback(btn_text, btn_data.to_string()) ]]); Ok(AnswerMessage::TextWithMarkup(msg_text, keyboard.into())) }