Files
book_bot/src/bots/approved_bot/modules/random.rs
2023-05-06 23:14:19 +02:00

365 lines
12 KiB
Rust

use strum_macros::{Display, EnumIter};
use teloxide::{
prelude::*,
types::{InlineKeyboardButton, InlineKeyboardMarkup},
utils::command::BotCommands, adaptors::Throttle,
};
use crate::bots::{
approved_bot::{
services::{
book_library::{self, formaters::Format},
user_settings::get_user_or_default_lang_codes,
},
tools::filter_callback_query,
},
BotHandlerInternal,
};
#[derive(BotCommands, Clone)]
#[command(rename_rule = "lowercase")]
enum RandomCommand {
Random,
}
#[derive(Clone, Display, EnumIter)]
#[strum(serialize_all = "snake_case")]
enum RandomCallbackData {
RandomBook,
RandomAuthor,
RandomSequence,
RandomBookByGenreRequest,
Genres { index: u32 },
RandomBookByGenre { id: u32 },
}
impl std::str::FromStr for RandomCallbackData {
type Err = strum::ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let value = s.to_string();
for callback_data in <RandomCallbackData as strum::IntoEnumIterator>::iter() {
match callback_data {
RandomCallbackData::Genres { .. }
| RandomCallbackData::RandomBookByGenre { .. } => {
let callback_prefix = callback_data.to_string();
if value.starts_with(&callback_prefix) {
let data: u32 = value
.strip_prefix(&format!("{}_", &callback_prefix).to_string())
.unwrap()
.parse()
.unwrap();
match callback_data {
RandomCallbackData::Genres { .. } => {
return Ok(RandomCallbackData::Genres { index: data })
}
RandomCallbackData::RandomBookByGenre { .. } => {
return Ok(RandomCallbackData::RandomBookByGenre { id: data })
}
_ => (),
}
}
}
_ => {
if value == callback_data.to_string() {
return Ok(callback_data);
}
}
}
}
return Err(strum::ParseError::VariantNotFound);
}
}
async fn random_handler(message: Message, bot: Throttle<Bot>) -> crate::bots::BotHandlerInternal {
const MESSAGE_TEXT: &str = "Что хотим получить?";
let keyboard = InlineKeyboardMarkup {
inline_keyboard: vec![
vec![InlineKeyboardButton {
kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(
RandomCallbackData::RandomBook.to_string(),
),
text: String::from("Книгу"),
}],
vec![InlineKeyboardButton {
kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(
RandomCallbackData::RandomBookByGenreRequest.to_string(),
),
text: String::from("Книгу по жанру"),
}],
vec![InlineKeyboardButton {
kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(
RandomCallbackData::RandomAuthor.to_string(),
),
text: String::from("Автора"),
}],
vec![InlineKeyboardButton {
kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(
RandomCallbackData::RandomSequence.to_string(),
),
text: String::from("Серию"),
}],
],
};
let res = bot
.send_message(message.chat.id, MESSAGE_TEXT)
.reply_to_message_id(message.id)
.reply_markup(keyboard)
.send()
.await;
match res {
Ok(_) => Ok(()),
Err(err) => Err(Box::new(err)),
}
}
async fn get_random_item_handler_internal<T>(
cq: CallbackQuery,
bot: Throttle<Bot>,
item: Result<T, Box<dyn std::error::Error + Send + Sync>>,
) -> BotHandlerInternal
where
T: Format,
{
match item {
Ok(item) => {
let item_message = item.format();
let send_item_handler = tokio::spawn(
bot.send_message(cq.from.id, item_message)
.reply_markup(InlineKeyboardMarkup {
inline_keyboard: vec![vec![InlineKeyboardButton {
kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(
cq.data.unwrap(),
),
text: String::from("Повторить?"),
}]],
})
.send(),
);
cq.message.map(|message| async move {
bot.edit_message_reply_markup(message.chat.id, message.id)
.reply_markup(InlineKeyboardMarkup {
inline_keyboard: vec![],
})
.send()
.await
});
match send_item_handler.await {
Ok(_) => Ok(()),
Err(err) => Err(Box::new(err)),
}
}
Err(err) => {
match bot
.send_message(cq.from.id, "Ошибка! Попробуйте позже :(")
.send()
.await
{
Ok(_) => (),
Err(int_error) => return Err(Box::new(int_error)),
}
return Err(err);
}
}
}
async fn get_random_item_handler<T, Fut>(
cq: CallbackQuery,
bot: Throttle<Bot>,
item_getter: fn(allowed_langs: Vec<String>) -> Fut,
) -> BotHandlerInternal
where
T: Format,
Fut: std::future::Future<Output = Result<T, Box<dyn std::error::Error + Send + Sync>>>,
{
let allowed_langs = get_user_or_default_lang_codes(cq.from.id).await;
let item = item_getter(allowed_langs).await;
get_random_item_handler_internal(cq, bot, item).await
}
async fn get_genre_metas_handler(cq: CallbackQuery, bot: Throttle<Bot>) -> BotHandlerInternal {
let genre_metas = match book_library::get_genre_metas().await {
Ok(v) => v,
Err(err) => return Err(err),
};
match cq.message {
Some(message) => {
let keyboard = InlineKeyboardMarkup {
inline_keyboard: genre_metas
.clone()
.into_iter()
.enumerate()
.map(|(index, genre_meta)| {
vec![InlineKeyboardButton {
kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(format!(
"{}_{index}",
RandomCallbackData::Genres {
index: index as u32
}
.to_string()
)),
text: genre_meta,
}]
})
.collect(),
};
match bot
.edit_message_reply_markup(message.chat.id, message.id)
.reply_markup(keyboard)
.send()
.await
{
Ok(_) => Ok(()),
Err(err) => Err(Box::new(err)),
}
}
None => {
match bot
.send_message(cq.from.id, "Ошибка! Начните заново :(")
.send()
.await
{
Ok(_) => Ok(()),
Err(err) => Err(Box::new(err)),
}
}
}
}
async fn get_genres_by_meta_handler(
cq: CallbackQuery,
bot: Throttle<Bot>,
genre_index: u32,
) -> BotHandlerInternal {
let genre_metas = match book_library::get_genre_metas().await {
Ok(v) => v,
Err(err) => return Err(err),
};
let meta = match genre_metas.get(genre_index as usize) {
Some(v) => v,
None => {
return match bot
.send_message(cq.from.id, "Ошибка! Попробуйте позже :(")
.send()
.await
{
Ok(_) => Ok(()),
Err(err) => Err(Box::new(err)),
}
}
};
let genres = match book_library::get_genres(meta.to_string()).await {
Ok(v) => v.items,
Err(err) => return Err(err),
};
let mut buttons: Vec<Vec<InlineKeyboardButton>> = genres
.clone()
.into_iter()
.map(|genre| {
vec![InlineKeyboardButton {
kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(format!(
"{}_{}",
RandomCallbackData::RandomBookByGenre { id: genre.id }.to_string(),
genre.id
)),
text: genre.description,
}]
})
.collect();
buttons.push(vec![InlineKeyboardButton {
kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(
RandomCallbackData::RandomBookByGenreRequest.to_string(),
),
text: "< Назад >".to_string(),
}]);
let keyboard = InlineKeyboardMarkup {
inline_keyboard: buttons,
};
match cq.message {
Some(message) => {
match bot
.edit_message_reply_markup(message.chat.id, message.id)
.reply_markup(keyboard)
.send()
.await
{
Ok(_) => Ok(()),
Err(err) => Err(Box::new(err)),
}
}
None => {
match bot
.send_message(cq.from.id, "Ошибка! Начните заново :(")
.send()
.await
{
Ok(_) => Ok(()),
Err(err) => Err(Box::new(err)),
}
}
}
}
async fn get_random_book_by_genre(
cq: CallbackQuery,
bot: Throttle<Bot>,
genre_id: u32,
) -> BotHandlerInternal {
let allowed_langs = get_user_or_default_lang_codes(cq.from.id).await;
let item = book_library::get_random_book_by_genre(allowed_langs, Some(genre_id)).await;
get_random_item_handler_internal(cq, bot, item).await
}
pub fn get_random_hander() -> crate::bots::BotHandler {
dptree::entry()
.branch(
Update::filter_message()
.branch(
dptree::entry()
.filter_command::<RandomCommand>()
.endpoint(|message, command, bot| async {
match command {
RandomCommand::Random => random_handler(message, bot).await,
}
})
)
)
.branch(
Update::filter_callback_query()
.chain(filter_callback_query::<RandomCallbackData>())
.endpoint(|cq: CallbackQuery, callback_data: RandomCallbackData, bot: Throttle<Bot>| async move {
match callback_data {
RandomCallbackData::RandomBook => get_random_item_handler(cq, bot, book_library::get_random_book).await,
RandomCallbackData::RandomAuthor => get_random_item_handler(cq, bot, book_library::get_random_author).await,
RandomCallbackData::RandomSequence => get_random_item_handler(cq, bot, book_library::get_random_sequence).await,
RandomCallbackData::RandomBookByGenreRequest => get_genre_metas_handler(cq, bot).await,
RandomCallbackData::Genres { index } => get_genres_by_meta_handler(cq, bot, index).await,
RandomCallbackData::RandomBookByGenre { id } => get_random_book_by_genre(cq, bot, id).await,
}
})
)
}