Skip to content

Commit

Permalink
Don't force a non-registered user to invoke command twice
Browse files Browse the repository at this point in the history
  • Loading branch information
Leonid Kozarin committed Jan 14, 2024
1 parent 0bdc0c4 commit 7efba1d
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 33 deletions.
2 changes: 1 addition & 1 deletion locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ registration:
button: "Accept"
consent:
ok: "Your account has been created successfully!"
appendix: "<i>Accepted by the <b>%{username}</b></i>\n\nThank you! Now you can invoke the command again."
appendix: "<i>Accepted by the <b>%{username}</b></i>\n\nThank you!"
set-option:
language:
success: "👌🏼👍🏼"
Expand Down
2 changes: 1 addition & 1 deletion locales/ru.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ registration:
button: "Принять"
consent:
ok: "Аккаунт успешно создан!"
appendix: "<i>Акцептировано <b>%{username}</b></i>\n\nБлагодарю за понимание! Теперь можно вызвать команду снова."
appendix: "<i>Акцептировано <b>%{username}</b></i>\n\nБлагодарю за понимание!"
set-option:
language:
success: "👌🏼👍🏼"
Expand Down
65 changes: 53 additions & 12 deletions src/handlers/options/consent.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,72 @@
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<String> for ConsentCallbackData {
type Error = InvalidConsentCallbackData;

fn try_from(value: String) -> Result<Self, Self::Error> {
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()
.map_err(|_| InvalidConsentCallbackData(value.clone()))?;
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<Self, Self::Err> {
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
Expand All @@ -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<UserServiceClientGrpc>) -> HandlerResult {
pub async fn callback_handler(bot: Bot, query: CallbackQuery, usr_client: UserService<UserServiceClientGrpc>,
dialogue_storage: Arc<InMemStorage<LocationState>>) -> 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,
Expand All @@ -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(())
}
35 changes: 20 additions & 15 deletions src/handlers/options/location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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(())
}

Expand Down Expand Up @@ -97,14 +86,30 @@ 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<USC: UserServiceClient>(user: &User, usr_client: UserService<USC>) -> anyhow::Result<MaybeContext<USC>> {
use MaybeContext::*;

let lang_code = ensure_lang_code(user.id, user.language_code.clone(), &usr_client.clone().into()).await;
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 }
}
Expand Down
9 changes: 5 additions & 4 deletions src/handlers/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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<AnswerMessage> {
async fn register_user(client: impl UserServiceClient, user: &teloxide::types::User, cmd: SavedSetCommand) -> anyhow::Result<AnswerMessage> {
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()))
}
Expand Down

0 comments on commit 7efba1d

Please sign in to comment.