diff --git a/src/bots/approved_bot/modules/annotations/callback_data.rs b/src/bots/approved_bot/modules/annotations/callback_data.rs index 28a5f62..66714a6 100644 --- a/src/bots/approved_bot/modules/annotations/callback_data.rs +++ b/src/bots/approved_bot/modules/annotations/callback_data.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use regex::Regex; -use crate::bots::approved_bot::modules::utils::GetPaginationCallbackData; +use crate::bots::approved_bot::modules::utils::{pagination::GetPaginationCallbackData, errors::CallbackQueryParseError}; #[derive(Debug, Clone)] @@ -12,13 +12,13 @@ pub enum AnnotationCallbackData { } impl FromStr for AnnotationCallbackData { - type Err = strum::ParseError; + type Err = CallbackQueryParseError; fn from_str(s: &str) -> Result { Regex::new(r"^(?P[ab])_an_(?P\d+)_(?P\d+)$") .unwrap_or_else(|_| panic!("Broken AnnotationCallbackData regex pattern!")) .captures(s) - .ok_or(strum::ParseError::VariantNotFound) + .ok_or(CallbackQueryParseError) .map(|caps| ( caps["an_type"].to_string(), caps["id"].parse::().unwrap(), diff --git a/src/bots/approved_bot/modules/annotations/commands.rs b/src/bots/approved_bot/modules/annotations/commands.rs index f1b76cc..42b7976 100644 --- a/src/bots/approved_bot/modules/annotations/commands.rs +++ b/src/bots/approved_bot/modules/annotations/commands.rs @@ -1,6 +1,6 @@ use regex::Regex; -use crate::bots::approved_bot::modules::utils::CommandParse; +use crate::bots::approved_bot::modules::utils::{filter_command::CommandParse, errors::CommandParseError}; #[derive(Debug, Clone)] @@ -10,28 +10,22 @@ pub enum AnnotationCommand { } impl CommandParse for AnnotationCommand { - fn parse(s: &str, bot_name: &str) -> Result { - let re = Regex::new(r"^/(?P[ab])_an_(?P\d+)$") - .unwrap_or_else(|_| panic!("Can't create AnnotationCommand regexp!")); - - let full_bot_name = format!("@{bot_name}"); - let after_replace = s.replace(&full_bot_name, ""); - - let caps = re.captures(&after_replace); - let caps = match caps { - Some(v) => v, - None => return Err(strum::ParseError::VariantNotFound), - }; - - let annotation_type = &caps["an_type"]; - let id: u32 = caps["id"] - .parse() - .unwrap_or_else(|_| panic!("Can't get id from AnnotationCommand!")); - - match annotation_type { - "a" => Ok(AnnotationCommand::Author { id }), - "b" => Ok(AnnotationCommand::Book { id }), - _ => Err(strum::ParseError::VariantNotFound), - } + fn parse(s: &str, bot_name: &str) -> Result { + Regex::new(r"^/(?P[ab])_an_(?P\d+)$") + .unwrap_or_else(|_| panic!("Broken AnnotationCommand regexp!")) + .captures( + &s.replace(&format!("@{bot_name}"), "") + ).ok_or(CommandParseError) + .map(|caps| ( + caps["an_type"].to_string(), + caps["id"].parse::().unwrap_or_else(|_| panic!("Can't get id from AnnotationCommand!")) + )) + .map(|(annotation_type, id)| { + match annotation_type.as_str() { + "a" => Ok(AnnotationCommand::Author { id }), + "b" => Ok(AnnotationCommand::Book { id }), + _ => panic!("Unknown AnnotationCommand type: {}!", annotation_type), + } + })? } } diff --git a/src/bots/approved_bot/modules/annotations/errors.rs b/src/bots/approved_bot/modules/annotations/errors.rs index dca7687..35f32a2 100644 --- a/src/bots/approved_bot/modules/annotations/errors.rs +++ b/src/bots/approved_bot/modules/annotations/errors.rs @@ -4,15 +4,15 @@ use super::commands::AnnotationCommand; #[derive(Debug)] -pub struct AnnotationError { +pub struct AnnotationFormatError { pub command: AnnotationCommand, pub text: String } -impl fmt::Display for AnnotationError { +impl fmt::Display for AnnotationFormatError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } -impl std::error::Error for AnnotationError {} \ No newline at end of file +impl std::error::Error for AnnotationFormatError {} diff --git a/src/bots/approved_bot/modules/annotations/mod.rs b/src/bots/approved_bot/modules/annotations/mod.rs index cda56d2..9ad72a9 100644 --- a/src/bots/approved_bot/modules/annotations/mod.rs +++ b/src/bots/approved_bot/modules/annotations/mod.rs @@ -12,7 +12,7 @@ use tokio_util::compat::FuturesAsyncReadCompatExt; use crate::bots::{ approved_bot::{ - modules::utils::generic_get_pagination_keyboard, + modules::utils::pagination::generic_get_pagination_keyboard, services::book_library::{ get_author_annotation, get_book_annotation, }, @@ -21,9 +21,9 @@ use crate::bots::{ BotHandlerInternal, }; -use self::{commands::AnnotationCommand, formatter::AnnotationFormat, callback_data::AnnotationCallbackData, errors::AnnotationError}; +use self::{commands::AnnotationCommand, formatter::AnnotationFormat, callback_data::AnnotationCallbackData, errors::AnnotationFormatError}; -use super::utils::{filter_command, split_text_to_chunks}; +use super::utils::{split_text::split_text_to_chunks, filter_command::filter_command}; async fn download_image( @@ -81,7 +81,7 @@ where }; if !annotation.is_normal_text() { - return Err(Box::new(AnnotationError { command, text: annotation.get_text().to_string() })); + return Err(Box::new(AnnotationFormatError { command, text: annotation.get_text().to_string() })); } let annotation_text = annotation.get_text(); diff --git a/src/bots/approved_bot/modules/book/callback_data.rs b/src/bots/approved_bot/modules/book/callback_data.rs index f1568e2..91ab0d2 100644 --- a/src/bots/approved_bot/modules/book/callback_data.rs +++ b/src/bots/approved_bot/modules/book/callback_data.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use regex::Regex; -use crate::bots::approved_bot::modules::utils::GetPaginationCallbackData; +use crate::bots::approved_bot::modules::utils::pagination::GetPaginationCallbackData; #[derive(Clone)] diff --git a/src/bots/approved_bot/modules/book/commands.rs b/src/bots/approved_bot/modules/book/commands.rs index 90a8c2f..46b375c 100644 --- a/src/bots/approved_bot/modules/book/commands.rs +++ b/src/bots/approved_bot/modules/book/commands.rs @@ -1,6 +1,6 @@ use regex::Regex; -use crate::bots::approved_bot::modules::utils::CommandParse; +use crate::bots::approved_bot::modules::utils::{filter_command::CommandParse, errors::CommandParseError}; #[derive(Clone)] @@ -11,7 +11,7 @@ pub enum BookCommand { } impl CommandParse for BookCommand { - fn parse(s: &str, bot_name: &str) -> Result { + fn parse(s: &str, bot_name: &str) -> Result { let re = Regex::new(r"^/(?P[ats])_(?P\d+)$").unwrap(); let full_bot_name = format!("@{bot_name}"); @@ -20,7 +20,7 @@ impl CommandParse for BookCommand { let caps = re.captures(&after_replace); let caps = match caps { Some(v) => v, - None => return Err(strum::ParseError::VariantNotFound), + None => return Err(CommandParseError), }; let annotation_type = &caps["an_type"]; @@ -30,7 +30,7 @@ impl CommandParse for BookCommand { "a" => Ok(BookCommand::Author { id }), "t" => Ok(BookCommand::Translator { id }), "s" => Ok(BookCommand::Sequence { id }), - _ => Err(strum::ParseError::VariantNotFound), + _ => Err(CommandParseError), } } } diff --git a/src/bots/approved_bot/modules/book/mod.rs b/src/bots/approved_bot/modules/book/mod.rs index fcd5ada..de4630a 100644 --- a/src/bots/approved_bot/modules/book/mod.rs +++ b/src/bots/approved_bot/modules/book/mod.rs @@ -22,9 +22,7 @@ use crate::bots::approved_bot::{ use self::{commands::BookCommand, callback_data::BookCallbackData}; -use super::utils::{ - filter_command, generic_get_pagination_keyboard -}; +use super::utils::{filter_command::filter_command, pagination::generic_get_pagination_keyboard}; async fn send_book_handler( diff --git a/src/bots/approved_bot/modules/download/commands.rs b/src/bots/approved_bot/modules/download/commands.rs index 2ea627f..cc5c7bc 100644 --- a/src/bots/approved_bot/modules/download/commands.rs +++ b/src/bots/approved_bot/modules/download/commands.rs @@ -1,7 +1,7 @@ use regex::Regex; use strum_macros::EnumIter; -use crate::bots::approved_bot::modules::utils::CommandParse; +use crate::bots::approved_bot::modules::utils::{filter_command::CommandParse, errors::CommandParseError}; #[derive(Clone)] @@ -17,7 +17,7 @@ impl ToString for StartDownloadCommand { } impl CommandParse for StartDownloadCommand { - fn parse(s: &str, bot_name: &str) -> Result { + fn parse(s: &str, bot_name: &str) -> Result { let re = Regex::new(r"^/d_(?P\d+)$").unwrap(); let full_bot_name = format!("@{bot_name}"); @@ -26,7 +26,7 @@ impl CommandParse for StartDownloadCommand { let caps = re.captures(&after_replace); let caps = match caps { Some(v) => v, - None => return Err(strum::ParseError::VariantNotFound), + None => return Err(CommandParseError), }; let book_id: u32 = caps["book_id"].parse().unwrap(); @@ -53,7 +53,7 @@ impl ToString for DownloadArchiveCommand { } impl CommandParse for DownloadArchiveCommand { - fn parse(s: &str, bot_name: &str) -> Result { + fn parse(s: &str, bot_name: &str) -> Result { let re = Regex::new(r"^/da_(?P[sat])_(?P\d+)$").unwrap(); let full_bot_name = format!("@{bot_name}"); @@ -62,7 +62,7 @@ impl CommandParse for DownloadArchiveCommand { let caps = re.captures(&after_replace); let caps = match caps { Some(v) => v, - None => return Err(strum::ParseError::VariantNotFound), + None => return Err(CommandParseError), }; let obj_id: u32 = caps["id"].parse().unwrap(); @@ -71,7 +71,7 @@ impl CommandParse for DownloadArchiveCommand { "s" => Ok(DownloadArchiveCommand::Sequence { id: obj_id }), "a" => Ok(DownloadArchiveCommand::Author { id: obj_id }), "t" => Ok(DownloadArchiveCommand::Translator { id: obj_id }), - _ => Err(strum::ParseError::VariantNotFound) + _ => Err(CommandParseError) } } } diff --git a/src/bots/approved_bot/modules/download/mod.rs b/src/bots/approved_bot/modules/download/mod.rs index 4baa086..b02db77 100644 --- a/src/bots/approved_bot/modules/download/mod.rs +++ b/src/bots/approved_bot/modules/download/mod.rs @@ -40,7 +40,7 @@ use crate::{ use self::{callback_data::{CheckArchiveStatus, DownloadQueryData}, commands::{StartDownloadCommand, DownloadArchiveCommand}}; -use super::utils::filter_command; +use super::utils::filter_command::filter_command; fn get_check_keyboard(task_id: String) -> InlineKeyboardMarkup { @@ -137,7 +137,7 @@ async fn send_with_download_from_channel( ) -> BotHandlerInternal { match download_file(&download_data).await { Ok(v) => { - if let Err(_) = _send_downloaded_file(&message, bot.clone(), v).await { + if _send_downloaded_file(&message, bot.clone(), v).await.is_err() { send_download_link(message.clone(), bot.clone(), download_data).await?; return Ok(()); }; diff --git a/src/bots/approved_bot/modules/search/callback_data.rs b/src/bots/approved_bot/modules/search/callback_data.rs index 4fd2431..118550a 100644 --- a/src/bots/approved_bot/modules/search/callback_data.rs +++ b/src/bots/approved_bot/modules/search/callback_data.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use regex::Regex; use strum_macros::EnumIter; -use crate::bots::approved_bot::modules::utils::GetPaginationCallbackData; +use crate::bots::approved_bot::modules::utils::pagination::GetPaginationCallbackData; #[derive(Clone, EnumIter)] diff --git a/src/bots/approved_bot/modules/search/mod.rs b/src/bots/approved_bot/modules/search/mod.rs index 19006f8..c43d3d5 100644 --- a/src/bots/approved_bot/modules/search/mod.rs +++ b/src/bots/approved_bot/modules/search/mod.rs @@ -26,7 +26,7 @@ use crate::bots::{ use self::{callback_data::SearchCallbackData, utils::get_query}; -use super::utils::generic_get_pagination_keyboard; +use super::utils::pagination::generic_get_pagination_keyboard; async fn generic_search_pagination_handler( diff --git a/src/bots/approved_bot/modules/update_history/callback_data.rs b/src/bots/approved_bot/modules/update_history/callback_data.rs index 1dcc076..208610d 100644 --- a/src/bots/approved_bot/modules/update_history/callback_data.rs +++ b/src/bots/approved_bot/modules/update_history/callback_data.rs @@ -4,7 +4,7 @@ use chrono::NaiveDate; use dateparser::parse; use regex::Regex; -use crate::bots::approved_bot::modules::utils::GetPaginationCallbackData; +use crate::bots::approved_bot::modules::utils::pagination::GetPaginationCallbackData; #[derive(Clone, Copy)] diff --git a/src/bots/approved_bot/modules/update_history/mod.rs b/src/bots/approved_bot/modules/update_history/mod.rs index 711f706..97edb91 100644 --- a/src/bots/approved_bot/modules/update_history/mod.rs +++ b/src/bots/approved_bot/modules/update_history/mod.rs @@ -16,7 +16,7 @@ use teloxide::{ use self::{commands::UpdateLogCommand, callback_data::UpdateLogCallbackData}; -use super::utils::generic_get_pagination_keyboard; +use super::utils::pagination::generic_get_pagination_keyboard; async fn update_log_command(message: Message, bot: CacheMe>) -> BotHandlerInternal { diff --git a/src/bots/approved_bot/modules/utils/errors.rs b/src/bots/approved_bot/modules/utils/errors.rs new file mode 100644 index 0000000..7b2d552 --- /dev/null +++ b/src/bots/approved_bot/modules/utils/errors.rs @@ -0,0 +1,25 @@ +use std::fmt; + + +#[derive(Debug)] +pub struct CallbackQueryParseError; + +impl fmt::Display for CallbackQueryParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for CallbackQueryParseError {} + + +#[derive(Debug)] +pub struct CommandParseError; + +impl fmt::Display for CommandParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for CommandParseError {} diff --git a/src/bots/approved_bot/modules/utils/filter_command.rs b/src/bots/approved_bot/modules/utils/filter_command.rs new file mode 100644 index 0000000..cfd92ca --- /dev/null +++ b/src/bots/approved_bot/modules/utils/filter_command.rs @@ -0,0 +1,21 @@ +use teloxide::{dptree, prelude::*, types::*}; + +use super::errors::CommandParseError; + + +pub trait CommandParse { + fn parse(s: &str, bot_name: &str) -> Result; +} + + +pub fn filter_command() -> crate::bots::BotHandler +where + Output: CommandParse + Send + Sync + 'static, +{ + dptree::entry().chain(dptree::filter_map(move |message: Message, me: Me| { + let bot_name = me.user.username.expect("Bots must have a username"); + message + .text() + .and_then(|text| Output::parse(text, &bot_name).ok()) + })) +} diff --git a/src/bots/approved_bot/modules/utils/mod.rs b/src/bots/approved_bot/modules/utils/mod.rs new file mode 100644 index 0000000..4b583be --- /dev/null +++ b/src/bots/approved_bot/modules/utils/mod.rs @@ -0,0 +1,4 @@ +pub mod pagination; +pub mod split_text; +pub mod filter_command; +pub mod errors; diff --git a/src/bots/approved_bot/modules/utils/pagination.rs b/src/bots/approved_bot/modules/utils/pagination.rs new file mode 100644 index 0000000..f9a0e36 --- /dev/null +++ b/src/bots/approved_bot/modules/utils/pagination.rs @@ -0,0 +1,101 @@ +use teloxide::types::{InlineKeyboardButton, InlineKeyboardMarkup}; + + +pub enum PaginationDelta { + OneMinus, + OnePlus, + FiveMinus, + FivePlus, +} + +pub trait GetPaginationCallbackData { + fn get_pagination_callback_data(&self, target_page: u32) -> String; +} + + +pub fn generic_get_pagination_button( + target_page: u32, + delta: PaginationDelta, + callback_data: &T, +) -> InlineKeyboardButton +where + T: GetPaginationCallbackData, +{ + let text = match delta { + PaginationDelta::OneMinus => "<", + PaginationDelta::OnePlus => ">", + PaginationDelta::FiveMinus => "< 5 <", + PaginationDelta::FivePlus => "> 5 >", + }; + + let callback_data = callback_data.get_pagination_callback_data(target_page); + + InlineKeyboardButton { + text: text.to_string(), + kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(callback_data), + } +} + + +pub fn generic_get_pagination_keyboard( + page: u32, + total_pages: u32, + search_data: T, + with_five: bool, +) -> InlineKeyboardMarkup +where + T: GetPaginationCallbackData, +{ + let buttons: Vec> = { + let t_page: i64 = page.into(); + + let mut result: Vec> = vec![]; + + let mut one_page_row: Vec = vec![]; + + if t_page - 1 > 0 { + one_page_row.push(generic_get_pagination_button( + page - 1, + PaginationDelta::OneMinus, + &search_data, + )) + } + if t_page < total_pages.into() { + one_page_row.push(generic_get_pagination_button( + page + 1, + PaginationDelta::OnePlus, + &search_data, + )) + } + if !one_page_row.is_empty() { + result.push(one_page_row); + } + + if with_five { + let mut five_page_row: Vec = vec![]; + if t_page - 5 > 0 { + five_page_row.push(generic_get_pagination_button( + page - 5, + PaginationDelta::FiveMinus, + &search_data, + )) + } + if t_page + 5 < total_pages.into() { + five_page_row.push(generic_get_pagination_button( + page + 5, + PaginationDelta::FivePlus, + &search_data, + )) + } + if !five_page_row.is_empty() { + result.push(five_page_row); + } + } + + result + }; + + InlineKeyboardMarkup { + inline_keyboard: buttons, + } +} diff --git a/src/bots/approved_bot/modules/utils.rs b/src/bots/approved_bot/modules/utils/split_text.rs similarity index 65% rename from src/bots/approved_bot/modules/utils.rs rename to src/bots/approved_bot/modules/utils/split_text.rs index 5f759d1..5bd493a 100644 --- a/src/bots/approved_bot/modules/utils.rs +++ b/src/bots/approved_bot/modules/utils/split_text.rs @@ -1,118 +1,3 @@ -use teloxide::{dptree, prelude::*, types::*}; - -pub trait CommandParse { - fn parse(s: &str, bot_name: &str) -> Result; -} - -pub fn filter_command() -> crate::bots::BotHandler -where - Output: CommandParse + Send + Sync + 'static, -{ - dptree::entry().chain(dptree::filter_map(move |message: Message, me: Me| { - let bot_name = me.user.username.expect("Bots must have a username"); - message - .text() - .and_then(|text| Output::parse(text, &bot_name).ok()) - })) -} - -pub enum PaginationDelta { - OneMinus, - OnePlus, - FiveMinus, - FivePlus, -} - -pub trait GetPaginationCallbackData { - fn get_pagination_callback_data(&self, target_page: u32) -> String; -} - -pub fn generic_get_pagination_button( - target_page: u32, - delta: PaginationDelta, - callback_data: &T, -) -> InlineKeyboardButton -where - T: GetPaginationCallbackData, -{ - let text = match delta { - PaginationDelta::OneMinus => "<", - PaginationDelta::OnePlus => ">", - PaginationDelta::FiveMinus => "< 5 <", - PaginationDelta::FivePlus => "> 5 >", - }; - - let callback_data = callback_data.get_pagination_callback_data(target_page); - - InlineKeyboardButton { - text: text.to_string(), - kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(callback_data), - } -} - -pub fn generic_get_pagination_keyboard( - page: u32, - total_pages: u32, - search_data: T, - with_five: bool, -) -> InlineKeyboardMarkup -where - T: GetPaginationCallbackData, -{ - let buttons: Vec> = { - let t_page: i64 = page.into(); - - let mut result: Vec> = vec![]; - - let mut one_page_row: Vec = vec![]; - - if t_page - 1 > 0 { - one_page_row.push(generic_get_pagination_button( - page - 1, - PaginationDelta::OneMinus, - &search_data, - )) - } - if t_page < total_pages.into() { - one_page_row.push(generic_get_pagination_button( - page + 1, - PaginationDelta::OnePlus, - &search_data, - )) - } - if !one_page_row.is_empty() { - result.push(one_page_row); - } - - if with_five { - let mut five_page_row: Vec = vec![]; - if t_page - 5 > 0 { - five_page_row.push(generic_get_pagination_button( - page - 5, - PaginationDelta::FiveMinus, - &search_data, - )) - } - if t_page + 5 < total_pages.into() { - five_page_row.push(generic_get_pagination_button( - page + 5, - PaginationDelta::FivePlus, - &search_data, - )) - } - if !five_page_row.is_empty() { - result.push(five_page_row); - } - } - - result - }; - - InlineKeyboardMarkup { - inline_keyboard: buttons, - } -} - pub fn split_text_to_chunks(text: &str, width: usize) -> Vec { let mut result: Vec = vec![]; @@ -141,9 +26,10 @@ pub fn split_text_to_chunks(text: &str, width: usize) -> Vec { result } + #[cfg(test)] mod tests { - use crate::bots::approved_bot::modules::utils::split_text_to_chunks; + use crate::bots::approved_bot::modules::utils::split_text::split_text_to_chunks; #[test] fn test_fix_annotation_text() { diff --git a/src/bots/approved_bot/tools.rs b/src/bots/approved_bot/tools.rs index b639ddc..80540ca 100644 --- a/src/bots/approved_bot/tools.rs +++ b/src/bots/approved_bot/tools.rs @@ -1,5 +1,6 @@ use teloxide::{dptree, types::CallbackQuery}; + pub fn filter_callback_query() -> crate::bots::BotHandler where T: std::str::FromStr + Send + Sync + 'static,