Add pre-commit

This commit is contained in:
2023-09-24 22:37:40 +02:00
parent 0afe3acfcd
commit 452040e83a
51 changed files with 771 additions and 596 deletions

7
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,7 @@
repos:
- repo: https://github.com/doublify/pre-commit-rust
rev: v1.0
hooks:
- id: fmt
- id: cargo-check
- id: clippy

View File

@@ -2,9 +2,16 @@ pub mod modules;
pub mod services; pub mod services;
mod tools; mod tools;
use teloxide::{prelude::*, types::BotCommand, adaptors::{Throttle, CacheMe}}; use teloxide::{
adaptors::{CacheMe, Throttle},
prelude::*,
types::BotCommand,
};
use crate::{bots::approved_bot::services::user_settings::create_or_update_user_settings, bots_manager::USER_ACTIVITY_CACHE}; use crate::{
bots::approved_bot::services::user_settings::create_or_update_user_settings,
bots_manager::USER_ACTIVITY_CACHE,
};
use self::{ use self::{
modules::{ modules::{
@@ -16,12 +23,12 @@ use self::{
services::user_settings::{get_user_or_default_lang_codes, update_user_activity}, services::user_settings::{get_user_or_default_lang_codes, update_user_activity},
}; };
use super::{ignore_channel_messages, BotCommands, BotHandler, bots_manager::get_manager_handler, ignore_chat_member_update}; use super::{
bots_manager::get_manager_handler, ignore_channel_messages, ignore_chat_member_update,
BotCommands, BotHandler,
};
async fn _update_activity( async fn _update_activity(me: teloxide::types::Me, user: teloxide::types::User) -> Option<()> {
me: teloxide::types::Me,
user: teloxide::types::User,
) -> Option<()> {
if USER_ACTIVITY_CACHE.contains_key(&user.id) { if USER_ACTIVITY_CACHE.contains_key(&user.id) {
return None; return None;
} }
@@ -39,7 +46,9 @@ async fn _update_activity(
user.username.clone().unwrap_or("".to_string()), user.username.clone().unwrap_or("".to_string()),
me.username.clone().unwrap(), me.username.clone().unwrap(),
allowed_langs, allowed_langs,
).await.is_ok() )
.await
.is_ok()
{ {
update_result = update_user_activity(user.id).await; update_result = update_user_activity(user.id).await;
} }

View File

@@ -2,8 +2,9 @@ use std::str::FromStr;
use regex::Regex; use regex::Regex;
use crate::bots::approved_bot::modules::utils::{pagination::GetPaginationCallbackData, errors::CallbackQueryParseError}; use crate::bots::approved_bot::modules::utils::{
errors::CallbackQueryParseError, pagination::GetPaginationCallbackData,
};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum AnnotationCallbackData { pub enum AnnotationCallbackData {
@@ -19,17 +20,20 @@ impl FromStr for AnnotationCallbackData {
.unwrap_or_else(|_| panic!("Broken AnnotationCallbackData regex pattern!")) .unwrap_or_else(|_| panic!("Broken AnnotationCallbackData regex pattern!"))
.captures(s) .captures(s)
.ok_or(CallbackQueryParseError) .ok_or(CallbackQueryParseError)
.map(|caps| ( .map(|caps| {
(
caps["an_type"].to_string(), caps["an_type"].to_string(),
caps["id"].parse::<u32>().unwrap(), caps["id"].parse::<u32>().unwrap(),
caps["page"].parse::<u32>().unwrap() caps["page"].parse::<u32>().unwrap(),
)) )
.map(|(annotation_type, id, page)| })
match annotation_type.as_str() { .map(
|(annotation_type, id, page)| match annotation_type.as_str() {
"a" => AnnotationCallbackData::Author { id, page }, "a" => AnnotationCallbackData::Author { id, page },
"b" => AnnotationCallbackData::Book { id, page }, "b" => AnnotationCallbackData::Book { id, page },
_ => panic!("Unknown AnnotationCallbackData type: {}!", annotation_type), _ => panic!("Unknown AnnotationCallbackData type: {}!", annotation_type),
}) },
)
} }
} }

View File

@@ -1,7 +1,8 @@
use regex::Regex; use regex::Regex;
use crate::bots::approved_bot::modules::utils::{filter_command::CommandParse, errors::CommandParseError}; use crate::bots::approved_bot::modules::utils::{
errors::CommandParseError, filter_command::CommandParse,
};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum AnnotationCommand { pub enum AnnotationCommand {
@@ -15,16 +16,18 @@ impl CommandParse<Self> for AnnotationCommand {
.unwrap_or_else(|_| panic!("Broken AnnotationCommand regexp!")) .unwrap_or_else(|_| panic!("Broken AnnotationCommand regexp!"))
.captures(&s.replace(&format!("@{bot_name}"), "")) .captures(&s.replace(&format!("@{bot_name}"), ""))
.ok_or(CommandParseError) .ok_or(CommandParseError)
.map(|caps| ( .map(|caps| {
(
caps["an_type"].to_string(), caps["an_type"].to_string(),
caps["id"].parse::<u32>().unwrap_or_else(|_| panic!("Can't get id from AnnotationCommand!")) caps["id"]
)) .parse::<u32>()
.map(|(annotation_type, id)| { .unwrap_or_else(|_| panic!("Can't get id from AnnotationCommand!")),
match annotation_type.as_str() { )
})
.map(|(annotation_type, id)| match annotation_type.as_str() {
"a" => Ok(AnnotationCommand::Author { id }), "a" => Ok(AnnotationCommand::Author { id }),
"b" => Ok(AnnotationCommand::Book { id }), "b" => Ok(AnnotationCommand::Book { id }),
_ => panic!("Unknown AnnotationCommand type: {}!", annotation_type), _ => panic!("Unknown AnnotationCommand type: {}!", annotation_type),
}
})? })?
} }
} }

View File

@@ -2,11 +2,10 @@ use std::fmt;
use super::commands::AnnotationCommand; use super::commands::AnnotationCommand;
#[derive(Debug)] #[derive(Debug)]
pub struct AnnotationFormatError { pub struct AnnotationFormatError {
pub command: AnnotationCommand, pub command: AnnotationCommand,
pub text: String pub text: String,
} }
impl fmt::Display for AnnotationFormatError { impl fmt::Display for AnnotationFormatError {

View File

@@ -21,7 +21,6 @@ impl AnnotationFormat for BookAnnotation {
} }
} }
impl AnnotationFormat for AuthorAnnotation { impl AnnotationFormat for AuthorAnnotation {
fn get_file(&self) -> Option<&String> { fn get_file(&self) -> Option<&String> {
self.file.as_ref() self.file.as_ref()

View File

@@ -1,30 +1,36 @@
pub mod commands;
pub mod callback_data; pub mod callback_data;
pub mod formatter; pub mod commands;
pub mod errors; pub mod errors;
pub mod formatter;
use std::convert::TryInto; use std::convert::TryInto;
use futures::TryStreamExt; use futures::TryStreamExt;
use teloxide::{dispatching::UpdateFilterExt, dptree, prelude::*, types::*, adaptors::{Throttle, CacheMe}}; use teloxide::{
adaptors::{CacheMe, Throttle},
dispatching::UpdateFilterExt,
dptree,
prelude::*,
types::*,
};
use tokio_util::compat::FuturesAsyncReadCompatExt; use tokio_util::compat::FuturesAsyncReadCompatExt;
use crate::bots::{ use crate::bots::{
approved_bot::{ approved_bot::{
modules::utils::pagination::generic_get_pagination_keyboard, modules::utils::pagination::generic_get_pagination_keyboard,
services::book_library::{ services::book_library::{get_author_annotation, get_book_annotation},
get_author_annotation, get_book_annotation,
},
tools::filter_callback_query, tools::filter_callback_query,
}, },
BotHandlerInternal, BotHandlerInternal,
}; };
use self::{commands::AnnotationCommand, formatter::AnnotationFormat, callback_data::AnnotationCallbackData, errors::AnnotationFormatError}; use self::{
callback_data::AnnotationCallbackData, commands::AnnotationCommand,
use super::utils::{split_text::split_text_to_chunks, filter_command::filter_command}; errors::AnnotationFormatError, formatter::AnnotationFormat,
};
use super::utils::{filter_command::filter_command, split_text::split_text_to_chunks};
async fn download_image( async fn download_image(
file: &String, file: &String,
@@ -32,7 +38,6 @@ async fn download_image(
Ok(reqwest::get(file).await?.error_for_status()?) Ok(reqwest::get(file).await?.error_for_status()?)
} }
pub async fn send_annotation_handler<T, Fut>( pub async fn send_annotation_handler<T, Fut>(
message: Message, message: Message,
bot: CacheMe<Throttle<Bot>>, bot: CacheMe<Throttle<Bot>>,
@@ -72,9 +77,9 @@ where
.into_async_read() .into_async_read()
.compat(); .compat();
#[allow(unused_must_use)] { #[allow(unused_must_use)]
bot {
.send_photo(message.chat.id, InputFile::read(data)) bot.send_photo(message.chat.id, InputFile::read(data))
.send() .send()
.await; .await;
} }
@@ -82,7 +87,10 @@ where
}; };
if !annotation.is_normal_text() { if !annotation.is_normal_text() {
return Err(Box::new(AnnotationFormatError { 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(); let annotation_text = annotation.get_text();
@@ -93,15 +101,10 @@ where
AnnotationCommand::Book { id } => AnnotationCallbackData::Book { id, page: 1 }, AnnotationCommand::Book { id } => AnnotationCallbackData::Book { id, page: 1 },
AnnotationCommand::Author { id } => AnnotationCallbackData::Author { id, page: 1 }, AnnotationCommand::Author { id } => AnnotationCallbackData::Author { id, page: 1 },
}; };
let keyboard = generic_get_pagination_keyboard( let keyboard =
1, generic_get_pagination_keyboard(1, chunked_text.len().try_into()?, callback_data, false);
chunked_text.len().try_into()?,
callback_data,
false,
);
bot bot.send_message(message.chat.id, current_text)
.send_message(message.chat.id, current_text)
.reply_markup(keyboard) .reply_markup(keyboard)
.send() .send()
.await?; .await?;
@@ -109,7 +112,6 @@ where
Ok(()) Ok(())
} }
pub async fn annotation_pagination_handler<T, Fut>( pub async fn annotation_pagination_handler<T, Fut>(
cq: CallbackQuery, cq: CallbackQuery,
bot: CacheMe<Throttle<Bot>>, bot: CacheMe<Throttle<Bot>>,
@@ -144,15 +146,10 @@ where
}; };
let current_text = chunked_text.get(page_index - 1).unwrap(); let current_text = chunked_text.get(page_index - 1).unwrap();
let keyboard = generic_get_pagination_keyboard( let keyboard =
page, generic_get_pagination_keyboard(page, chunked_text.len().try_into()?, callback_data, false);
chunked_text.len().try_into()?,
callback_data,
false,
);
bot bot.edit_message_text(message.chat.id, message.id, current_text)
.edit_message_text(message.chat.id, message.id, current_text)
.reply_markup(keyboard) .reply_markup(keyboard)
.send() .send()
.await?; .await?;
@@ -160,7 +157,6 @@ where
Ok(()) Ok(())
} }
pub fn get_annotations_handler() -> crate::bots::BotHandler { pub fn get_annotations_handler() -> crate::bots::BotHandler {
dptree::entry() dptree::entry()
.branch( .branch(

View File

@@ -2,8 +2,9 @@ use std::str::FromStr;
use regex::Regex; use regex::Regex;
use crate::bots::approved_bot::modules::utils::{pagination::GetPaginationCallbackData, errors::CallbackQueryParseError}; use crate::bots::approved_bot::modules::utils::{
errors::CallbackQueryParseError, pagination::GetPaginationCallbackData,
};
#[derive(Clone)] #[derive(Clone)]
pub enum BookCallbackData { pub enum BookCallbackData {
@@ -20,18 +21,20 @@ impl FromStr for BookCallbackData {
.unwrap_or_else(|_| panic!("Broken BookCallbackData regex pattern!")) .unwrap_or_else(|_| panic!("Broken BookCallbackData regex pattern!"))
.captures(s) .captures(s)
.ok_or(CallbackQueryParseError) .ok_or(CallbackQueryParseError)
.map(|caps| ( .map(|caps| {
(
caps["an_type"].to_string(), caps["an_type"].to_string(),
caps["id"].parse::<u32>().unwrap(), caps["id"].parse::<u32>().unwrap(),
caps["page"].parse::<u32>().unwrap() caps["page"].parse::<u32>().unwrap(),
)) )
.map(|(annotation_type, id, page)| })
match annotation_type.as_str() { .map(
|(annotation_type, id, page)| match annotation_type.as_str() {
"a" => Ok(BookCallbackData::Author { id, page }), "a" => Ok(BookCallbackData::Author { id, page }),
"t" => Ok(BookCallbackData::Translator { id, page }), "t" => Ok(BookCallbackData::Translator { id, page }),
"s" => Ok(BookCallbackData::Sequence { id, page }), "s" => Ok(BookCallbackData::Sequence { id, page }),
_ => panic!("Unknown BookCallbackData type: {}!", annotation_type), _ => panic!("Unknown BookCallbackData type: {}!", annotation_type),
} },
)? )?
} }
} }

View File

@@ -1,7 +1,8 @@
use regex::Regex; use regex::Regex;
use crate::bots::approved_bot::modules::utils::{filter_command::CommandParse, errors::CommandParseError}; use crate::bots::approved_bot::modules::utils::{
errors::CommandParseError, filter_command::CommandParse,
};
#[derive(Clone)] #[derive(Clone)]
pub enum BookCommand { pub enum BookCommand {
@@ -16,17 +17,12 @@ impl CommandParse<Self> for BookCommand {
.unwrap_or_else(|_| panic!("Broken BookCommand regexp!")) .unwrap_or_else(|_| panic!("Broken BookCommand regexp!"))
.captures(&s.replace(&format!("@{bot_name}"), "")) .captures(&s.replace(&format!("@{bot_name}"), ""))
.ok_or(CommandParseError) .ok_or(CommandParseError)
.map(|caps| ( .map(|caps| (caps["an_type"].to_string(), caps["id"].parse().unwrap()))
caps["an_type"].to_string(), .map(|(annotation_type, id)| match annotation_type.as_str() {
caps["id"].parse().unwrap()
))
.map(|(annotation_type, id)| {
match annotation_type.as_str() {
"a" => Ok(BookCommand::Author { id }), "a" => Ok(BookCommand::Author { id }),
"t" => Ok(BookCommand::Translator { id }), "t" => Ok(BookCommand::Translator { id }),
"s" => Ok(BookCommand::Sequence { id }), "s" => Ok(BookCommand::Sequence { id }),
_ => panic!("Unknown BookCommand type: {}!", annotation_type), _ => panic!("Unknown BookCommand type: {}!", annotation_type),
}
})? })?
} }
} }

View File

@@ -1,18 +1,24 @@
pub mod commands;
pub mod callback_data; pub mod callback_data;
pub mod commands;
use core::fmt::Debug; use core::fmt::Debug;
use smartstring::alias::String as SmartString; use smartstring::alias::String as SmartString;
use smallvec::SmallVec; use smallvec::SmallVec;
use teloxide::{dispatching::UpdateFilterExt, dptree, prelude::*, adaptors::{Throttle, CacheMe}}; use teloxide::{
adaptors::{CacheMe, Throttle},
dispatching::UpdateFilterExt,
dptree,
prelude::*,
};
use tracing::log; use tracing::log;
use crate::bots::approved_bot::{ use crate::bots::approved_bot::{
services::{ services::{
book_library::{ book_library::{
formatters::{Format, FormatTitle}, get_author_books, get_sequence_books, get_translator_books, formatters::{Format, FormatTitle},
get_author_books, get_sequence_books, get_translator_books,
types::Page, types::Page,
}, },
user_settings::get_user_or_default_lang_codes, user_settings::get_user_or_default_lang_codes,
@@ -20,11 +26,10 @@ use crate::bots::approved_bot::{
tools::filter_callback_query, tools::filter_callback_query,
}; };
use self::{commands::BookCommand, callback_data::BookCallbackData}; use self::{callback_data::BookCallbackData, commands::BookCommand};
use super::utils::{filter_command::filter_command, pagination::generic_get_pagination_keyboard}; use super::utils::{filter_command::filter_command, pagination::generic_get_pagination_keyboard};
async fn send_book_handler<T, P, Fut>( async fn send_book_handler<T, P, Fut>(
message: Message, message: Message,
bot: CacheMe<Throttle<Bot>>, bot: CacheMe<Throttle<Bot>>,
@@ -64,8 +69,7 @@ where
let items_page = match books_getter(id, 1, allowed_langs.clone()).await { let items_page = match books_getter(id, 1, allowed_langs.clone()).await {
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
bot bot.send_message(chat_id, "Ошибка! Попробуйте позже :(")
.send_message(chat_id, "Ошибка! Попробуйте позже :(")
.send() .send()
.await?; .await?;
return Err(err); return Err(err);
@@ -73,7 +77,9 @@ where
}; };
if items_page.pages == 0 { if items_page.pages == 0 {
bot.send_message(chat_id, "Книги не найдены!").send().await?; bot.send_message(chat_id, "Книги не найдены!")
.send()
.await?;
return Ok(()); return Ok(());
}; };
@@ -87,8 +93,7 @@ where
let keyboard = generic_get_pagination_keyboard(1, items_page.pages, callback_data, true); let keyboard = generic_get_pagination_keyboard(1, items_page.pages, callback_data, true);
bot bot.send_message(chat_id, formatted_page)
.send_message(chat_id, formatted_page)
.reply_markup(keyboard) .reply_markup(keyboard)
.send() .send()
.await?; .await?;
@@ -120,9 +125,11 @@ where
let (chat_id, message_id) = match (chat_id, message_id) { let (chat_id, message_id) = match (chat_id, message_id) {
(Some(chat_id), Some(message_id)) => (chat_id, message_id), (Some(chat_id), Some(message_id)) => (chat_id, message_id),
(Some(chat_id), None) => { (Some(chat_id), None) => {
bot.send_message(chat_id, "Повторите поиск сначала").send().await?; bot.send_message(chat_id, "Повторите поиск сначала")
.send()
.await?;
return Ok(()); return Ok(());
}, }
_ => { _ => {
return Ok(()); return Ok(());
} }
@@ -146,7 +153,9 @@ where
}; };
if items_page.pages == 0 { if items_page.pages == 0 {
bot.send_message(chat_id, "Книги не найдены!").send().await?; bot.send_message(chat_id, "Книги не найдены!")
.send()
.await?;
return Ok(()); return Ok(());
}; };
@@ -154,8 +163,7 @@ where
items_page = match books_getter(id, items_page.pages, allowed_langs.clone()).await { items_page = match books_getter(id, items_page.pages, allowed_langs.clone()).await {
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
bot bot.send_message(chat_id, "Ошибка! Попробуйте позже :(")
.send_message(chat_id, "Ошибка! Попробуйте позже :(")
.send() .send()
.await?; .await?;
@@ -168,8 +176,7 @@ where
let keyboard = generic_get_pagination_keyboard(page, items_page.pages, callback_data, true); let keyboard = generic_get_pagination_keyboard(page, items_page.pages, callback_data, true);
bot bot.edit_message_text(chat_id, message_id, formatted_page)
.edit_message_text(chat_id, message_id, formatted_page)
.reply_markup(keyboard) .reply_markup(keyboard)
.send() .send()
.await?; .await?;

View File

@@ -5,7 +5,6 @@ use strum_macros::EnumIter;
use crate::bots::approved_bot::modules::utils::errors::CommandParseError; use crate::bots::approved_bot::modules::utils::errors::CommandParseError;
#[derive(Clone, EnumIter)] #[derive(Clone, EnumIter)]
pub enum DownloadQueryData { pub enum DownloadQueryData {
DownloadData { book_id: u32, file_type: String }, DownloadData { book_id: u32, file_type: String },
@@ -29,13 +28,13 @@ impl FromStr for DownloadQueryData {
.unwrap_or_else(|_| panic!("Broken DownloadQueryData regexp!")) .unwrap_or_else(|_| panic!("Broken DownloadQueryData regexp!"))
.captures(s) .captures(s)
.ok_or(CommandParseError) .ok_or(CommandParseError)
.map(|caps| ( .map(|caps| {
(
caps["book_id"].parse().unwrap(), caps["book_id"].parse().unwrap(),
caps["file_type"].to_string() caps["file_type"].to_string(),
)) )
.map(|(book_id, file_type)| {
DownloadQueryData::DownloadData { book_id, file_type }
}) })
.map(|(book_id, file_type)| DownloadQueryData::DownloadData { book_id, file_type })
} }
} }
@@ -43,15 +42,19 @@ impl FromStr for DownloadQueryData {
pub enum DownloadArchiveQueryData { pub enum DownloadArchiveQueryData {
Sequence { id: u32, file_type: String }, Sequence { id: u32, file_type: String },
Author { id: u32, file_type: String }, Author { id: u32, file_type: String },
Translator { id: u32, file_type: String } Translator { id: u32, file_type: String },
} }
impl ToString for DownloadArchiveQueryData { impl ToString for DownloadArchiveQueryData {
fn to_string(&self) -> String { fn to_string(&self) -> String {
match self { match self {
DownloadArchiveQueryData::Sequence { id, file_type } => format!("da_s_{id}_{file_type}"), DownloadArchiveQueryData::Sequence { id, file_type } => {
format!("da_s_{id}_{file_type}")
}
DownloadArchiveQueryData::Author { id, file_type } => format!("da_a_{id}_{file_type}"), DownloadArchiveQueryData::Author { id, file_type } => format!("da_a_{id}_{file_type}"),
DownloadArchiveQueryData::Translator { id, file_type } => format!("da_t_{id}_{file_type}"), DownloadArchiveQueryData::Translator { id, file_type } => {
format!("da_t_{id}_{file_type}")
}
} }
} }
} }
@@ -71,20 +74,18 @@ impl FromStr for DownloadArchiveQueryData {
let id: u32 = caps["id"].parse().unwrap(); let id: u32 = caps["id"].parse().unwrap();
let file_type: String = caps["file_type"].to_string(); let file_type: String = caps["file_type"].to_string();
Ok( Ok(match caps["obj_type"].to_string().as_str() {
match caps["obj_type"].to_string().as_str() {
"s" => DownloadArchiveQueryData::Sequence { id, file_type }, "s" => DownloadArchiveQueryData::Sequence { id, file_type },
"a" => DownloadArchiveQueryData::Author { id, file_type }, "a" => DownloadArchiveQueryData::Author { id, file_type },
"t" => DownloadArchiveQueryData::Translator { id, file_type }, "t" => DownloadArchiveQueryData::Translator { id, file_type },
_ => return Err(strum::ParseError::VariantNotFound) _ => return Err(strum::ParseError::VariantNotFound),
} })
)
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub struct CheckArchiveStatus { pub struct CheckArchiveStatus {
pub task_id: String pub task_id: String,
} }
impl ToString for CheckArchiveStatus { impl ToString for CheckArchiveStatus {

View File

@@ -1,8 +1,9 @@
use regex::Regex; use regex::Regex;
use strum_macros::EnumIter; use strum_macros::EnumIter;
use crate::bots::approved_bot::modules::utils::{filter_command::CommandParse, errors::CommandParseError}; use crate::bots::approved_bot::modules::utils::{
errors::CommandParseError, filter_command::CommandParse,
};
#[derive(Clone)] #[derive(Clone)]
pub struct StartDownloadCommand { pub struct StartDownloadCommand {
@@ -37,9 +38,9 @@ impl CommandParse<Self> for StartDownloadCommand {
#[derive(Clone, EnumIter)] #[derive(Clone, EnumIter)]
pub enum DownloadArchiveCommand { pub enum DownloadArchiveCommand {
Sequence { id: u32}, Sequence { id: u32 },
Author { id: u32 }, Author { id: u32 },
Translator { id: u32 } Translator { id: u32 },
} }
impl ToString for DownloadArchiveCommand { impl ToString for DownloadArchiveCommand {
@@ -71,7 +72,7 @@ impl CommandParse<Self> for DownloadArchiveCommand {
"s" => Ok(DownloadArchiveCommand::Sequence { id: obj_id }), "s" => Ok(DownloadArchiveCommand::Sequence { id: obj_id }),
"a" => Ok(DownloadArchiveCommand::Author { id: obj_id }), "a" => Ok(DownloadArchiveCommand::Author { id: obj_id }),
"t" => Ok(DownloadArchiveCommand::Translator { id: obj_id }), "t" => Ok(DownloadArchiveCommand::Translator { id: obj_id }),
_ => Err(CommandParseError) _ => Err(CommandParseError),
} }
} }
} }

View File

@@ -1,12 +1,11 @@
pub mod commands;
pub mod callback_data; pub mod callback_data;
pub mod commands;
use std::time::Duration; use std::time::Duration;
use chrono::Utc; use chrono::Utc;
use futures::TryStreamExt; use futures::TryStreamExt;
use teloxide::{ use teloxide::{
adaptors::{CacheMe, Throttle}, adaptors::{CacheMe, Throttle},
dispatching::UpdateFilterExt, dispatching::UpdateFilterExt,
@@ -21,42 +20,46 @@ use tracing::log;
use crate::{ use crate::{
bots::{ bots::{
approved_bot::{ approved_bot::{
modules::download::callback_data::DownloadArchiveQueryData,
services::{ services::{
batch_downloader::{create_task, get_task, Task, TaskStatus},
batch_downloader::{CreateTaskData, TaskObjectType},
book_cache::{ book_cache::{
download_file, get_cached_message, download_file, download_file_by_link, get_cached_message, get_download_link,
types::{CachedMessage, DownloadFile}, download_file_by_link, get_download_link, types::{CachedMessage, DownloadFile},
}, },
book_library::{get_book, get_author_books_available_types, get_translator_books_available_types, get_sequence_books_available_types}, book_library::{
donation_notifications::send_donation_notification, user_settings::get_user_or_default_lang_codes, batch_downloader::{TaskObjectType, CreateTaskData}, get_author_books_available_types, get_book, get_sequence_books_available_types,
batch_downloader::{create_task, get_task, TaskStatus, Task} get_translator_books_available_types,
}, },
tools::filter_callback_query, modules::download::callback_data::DownloadArchiveQueryData, donation_notifications::send_donation_notification,
user_settings::get_user_or_default_lang_codes,
},
tools::filter_callback_query,
}, },
BotHandlerInternal, BotHandlerInternal,
}, },
bots_manager::BotCache, bots_manager::BotCache,
}; };
use self::{callback_data::{CheckArchiveStatus, DownloadQueryData}, commands::{StartDownloadCommand, DownloadArchiveCommand}}; use self::{
callback_data::{CheckArchiveStatus, DownloadQueryData},
commands::{DownloadArchiveCommand, StartDownloadCommand},
};
use super::utils::filter_command::filter_command; use super::utils::filter_command::filter_command;
fn get_check_keyboard(task_id: String) -> InlineKeyboardMarkup { fn get_check_keyboard(task_id: String) -> InlineKeyboardMarkup {
InlineKeyboardMarkup { InlineKeyboardMarkup {
inline_keyboard: vec![ inline_keyboard: vec![vec![InlineKeyboardButton {
vec![InlineKeyboardButton {
kind: teloxide::types::InlineKeyboardButtonKind::CallbackData( kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(
(CheckArchiveStatus { task_id }).to_string(), (CheckArchiveStatus { task_id }).to_string(),
), ),
text: String::from("Обновить статус"), text: String::from("Обновить статус"),
}], }]],
],
} }
} }
async fn _send_cached( async fn _send_cached(
message: &Message, message: &Message,
bot: &CacheMe<Throttle<Bot>>, bot: &CacheMe<Throttle<Bot>>,
@@ -94,8 +97,7 @@ async fn send_cached_message(
} }
}; };
send_with_download_from_channel(message, bot, download_data, need_delete_message) send_with_download_from_channel(message, bot, download_data, need_delete_message).await?;
.await?;
Ok(()) Ok(())
} }
@@ -137,7 +139,10 @@ async fn send_with_download_from_channel(
) -> BotHandlerInternal { ) -> BotHandlerInternal {
match download_file(&download_data).await { match download_file(&download_data).await {
Ok(v) => { Ok(v) => {
if _send_downloaded_file(&message, bot.clone(), v).await.is_err() { if _send_downloaded_file(&message, bot.clone(), v)
.await
.is_err()
{
send_download_link(message.clone(), bot.clone(), download_data).await?; send_download_link(message.clone(), bot.clone(), download_data).await?;
return Ok(()); return Ok(());
}; };
@@ -147,7 +152,7 @@ async fn send_with_download_from_channel(
} }
Ok(()) Ok(())
}, }
Err(err) => Err(err), Err(err) => Err(err),
} }
} }
@@ -162,18 +167,17 @@ async fn send_download_link(
Err(err) => { Err(err) => {
log::error!("{:?}", err); log::error!("{:?}", err);
return Err(err); return Err(err);
}, }
}; };
bot bot.edit_message_text(
.edit_message_text(
message.chat.id, message.chat.id,
message.id, message.id,
format!( format!(
"Файл не может быть загружен в чат! \n \ "Файл не может быть загружен в чат! \n \
Вы можете скачать его <a href=\"{}\">по ссылке</a> (работает 3 часа)", Вы можете скачать его <a href=\"{}\">по ссылке</a> (работает 3 часа)",
link_data.link link_data.link
) ),
) )
.parse_mode(ParseMode::Html) .parse_mode(ParseMode::Html)
.reply_markup(InlineKeyboardMarkup { .reply_markup(InlineKeyboardMarkup {
@@ -193,22 +197,10 @@ async fn download_handler(
) -> BotHandlerInternal { ) -> BotHandlerInternal {
match cache { match cache {
BotCache::Original => { BotCache::Original => {
send_cached_message( send_cached_message(message, bot, download_data, need_delete_message).await
message,
bot,
download_data,
need_delete_message,
)
.await
} }
BotCache::NoCache => { BotCache::NoCache => {
send_with_download_from_channel( send_with_download_from_channel(message, bot, download_data, need_delete_message).await
message,
bot,
download_data,
need_delete_message,
)
.await
} }
} }
} }
@@ -235,9 +227,7 @@ async fn get_download_keyboard_handler(
.into_iter() .into_iter()
.map(|item| -> Vec<InlineKeyboardButton> { .map(|item| -> Vec<InlineKeyboardButton> {
vec![InlineKeyboardButton { vec![InlineKeyboardButton {
text: { text: { format!("📥 {item}") },
format!("📥 {item}")
},
kind: InlineKeyboardButtonKind::CallbackData( kind: InlineKeyboardButtonKind::CallbackData(
(DownloadQueryData::DownloadData { (DownloadQueryData::DownloadData {
book_id: book.id, book_id: book.id,
@@ -264,14 +254,18 @@ async fn get_download_archive_keyboard_handler(
bot: CacheMe<Throttle<Bot>>, bot: CacheMe<Throttle<Bot>>,
command: DownloadArchiveCommand, command: DownloadArchiveCommand,
) -> BotHandlerInternal { ) -> BotHandlerInternal {
let allowed_langs = get_user_or_default_lang_codes( let allowed_langs = get_user_or_default_lang_codes(message.from().unwrap().id).await;
message.from().unwrap().id,
).await;
let available_types = match command { let available_types = match command {
DownloadArchiveCommand::Sequence { id } => get_sequence_books_available_types(id, allowed_langs).await, DownloadArchiveCommand::Sequence { id } => {
DownloadArchiveCommand::Author { id } => get_author_books_available_types(id, allowed_langs).await, get_sequence_books_available_types(id, allowed_langs).await
DownloadArchiveCommand::Translator { id } => get_translator_books_available_types(id, allowed_langs).await, }
DownloadArchiveCommand::Author { id } => {
get_author_books_available_types(id, allowed_langs).await
}
DownloadArchiveCommand::Translator { id } => {
get_translator_books_available_types(id, allowed_langs).await
}
}; };
let available_types = match available_types { let available_types = match available_types {
@@ -280,31 +274,39 @@ async fn get_download_archive_keyboard_handler(
}; };
let keyboard = InlineKeyboardMarkup { let keyboard = InlineKeyboardMarkup {
inline_keyboard: inline_keyboard: available_types
available_types.iter() .iter()
.filter(|file_type| !file_type.contains("zip")) .filter(|file_type| !file_type.contains("zip"))
.map(|file_type| { .map(|file_type| {
let callback_data: String = match command { let callback_data: String = match command {
DownloadArchiveCommand::Sequence { id } => DownloadArchiveQueryData::Sequence { DownloadArchiveCommand::Sequence { id } => DownloadArchiveQueryData::Sequence {
id, file_type: file_type.to_string() id,
}.to_string(), file_type: file_type.to_string(),
}
.to_string(),
DownloadArchiveCommand::Author { id } => DownloadArchiveQueryData::Author { DownloadArchiveCommand::Author { id } => DownloadArchiveQueryData::Author {
id, file_type: file_type.to_string() id,
}.to_string(), file_type: file_type.to_string(),
DownloadArchiveCommand::Translator { id } => DownloadArchiveQueryData::Translator { }
id, file_type: file_type.to_string() .to_string(),
}.to_string(), DownloadArchiveCommand::Translator { id } => {
DownloadArchiveQueryData::Translator {
id,
file_type: file_type.to_string(),
}
.to_string()
}
}; };
vec![InlineKeyboardButton { vec![InlineKeyboardButton {
text: file_type.to_string(), text: file_type.to_string(),
kind: InlineKeyboardButtonKind::CallbackData(callback_data) kind: InlineKeyboardButtonKind::CallbackData(callback_data),
}] }]
}).collect() })
.collect(),
}; };
bot bot.send_message(message.chat.id, "Выбери формат:")
.send_message(message.chat.id, "Выбери формат:")
.reply_markup(keyboard) .reply_markup(keyboard)
.reply_to_message_id(message.id) .reply_to_message_id(message.id)
.await?; .await?;
@@ -327,15 +329,14 @@ async fn send_archive_link(
message: Message, message: Message,
task: Task, task: Task,
) -> BotHandlerInternal { ) -> BotHandlerInternal {
bot bot.edit_message_text(
.edit_message_text(
message.chat.id, message.chat.id,
message.id, message.id,
format!( format!(
"Файл не может быть загружен в чат! \n \ "Файл не может быть загружен в чат! \n \
Вы можете скачать его <a href=\"{}\">по ссылке</a> (работает 3 часа)", Вы можете скачать его <a href=\"{}\">по ссылке</a> (работает 3 часа)",
task.result_link.unwrap() task.result_link.unwrap()
) ),
) )
.parse_mode(ParseMode::Html) .parse_mode(ParseMode::Html)
.reply_markup(InlineKeyboardMarkup { .reply_markup(InlineKeyboardMarkup {
@@ -362,7 +363,7 @@ async fn wait_archive(
send_error_message(bot, message.chat.id, message.id).await; send_error_message(bot, message.chat.id, message.id).await;
log::error!("{:?}", err); log::error!("{:?}", err);
return Err(err); return Err(err);
}, }
}; };
if task.status != TaskStatus::InProgress { if task.status != TaskStatus::InProgress {
@@ -371,14 +372,13 @@ async fn wait_archive(
let now = Utc::now().format("%H:%M:%S UTC").to_string(); let now = Utc::now().format("%H:%M:%S UTC").to_string();
bot bot.edit_message_text(
.edit_message_text(
message.chat.id, message.chat.id,
message.id, message.id,
format!( format!(
"Статус: \n{} \n\nОбновлено в {now}", "Статус: \n{} \n\nОбновлено в {now}",
task.status_description task.status_description
) ),
) )
.reply_markup(get_check_keyboard(task.id)) .reply_markup(get_check_keyboard(task.id))
.send() .send()
@@ -394,53 +394,52 @@ async fn wait_archive(
if content_size > 20 * 1024 * 1024 { if content_size > 20 * 1024 * 1024 {
send_archive_link(bot.clone(), message.clone(), task.clone()).await?; send_archive_link(bot.clone(), message.clone(), task.clone()).await?;
return Ok(()) return Ok(());
} }
let downloaded_data = match download_file_by_link( let downloaded_data = match download_file_by_link(
task.clone().result_filename.unwrap(), task.clone().result_filename.unwrap(),
task.result_link.clone().unwrap() task.result_link.clone().unwrap(),
).await { )
.await
{
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
send_error_message(bot, message.chat.id, message.id).await; send_error_message(bot, message.chat.id, message.id).await;
log::error!("{:?}", err); log::error!("{:?}", err);
return Err(err); return Err(err);
}, }
}; };
match _send_downloaded_file( match _send_downloaded_file(&message, bot.clone(), downloaded_data).await {
&message,
bot.clone(),
downloaded_data,
).await {
Ok(_) => (), Ok(_) => (),
Err(_) => { Err(_) => {
send_archive_link(bot.clone(), message.clone(), task).await?; send_archive_link(bot.clone(), message.clone(), task).await?;
}, }
} }
bot bot.delete_message(message.chat.id, message.id).await?;
.delete_message(message.chat.id, message.id)
.await?;
Ok(()) Ok(())
} }
async fn download_archive( async fn download_archive(
cq: CallbackQuery, cq: CallbackQuery,
download_archive_query_data: DownloadArchiveQueryData, download_archive_query_data: DownloadArchiveQueryData,
bot: CacheMe<Throttle<Bot>>, bot: CacheMe<Throttle<Bot>>,
) -> BotHandlerInternal { ) -> BotHandlerInternal {
let allowed_langs = get_user_or_default_lang_codes( let allowed_langs = get_user_or_default_lang_codes(cq.from.id).await;
cq.from.id,
).await;
let (id, file_type, task_type) = match download_archive_query_data { let (id, file_type, task_type) = match download_archive_query_data {
DownloadArchiveQueryData::Sequence { id, file_type } => (id, file_type, TaskObjectType::Sequence), DownloadArchiveQueryData::Sequence { id, file_type } => {
DownloadArchiveQueryData::Author { id, file_type } => (id, file_type, TaskObjectType::Author), (id, file_type, TaskObjectType::Sequence)
DownloadArchiveQueryData::Translator { id, file_type } => (id, file_type, TaskObjectType::Translator), }
DownloadArchiveQueryData::Author { id, file_type } => {
(id, file_type, TaskObjectType::Author)
}
DownloadArchiveQueryData::Translator { id, file_type } => {
(id, file_type, TaskObjectType::Translator)
}
}; };
let message = cq.message.unwrap(); let message = cq.message.unwrap();
@@ -450,7 +449,8 @@ async fn download_archive(
object_type: task_type, object_type: task_type,
file_format: file_type, file_format: file_type,
allowed_langs, allowed_langs,
}).await; })
.await;
let task = match task { let task = match task {
Ok(v) => v, Ok(v) => v,
@@ -458,11 +458,10 @@ async fn download_archive(
send_error_message(bot, message.chat.id, message.id).await; send_error_message(bot, message.chat.id, message.id).await;
log::error!("{:?}", err); log::error!("{:?}", err);
return Err(err); return Err(err);
}, }
}; };
bot bot.edit_message_text(message.chat.id, message.id, "⏳ Подготовка архива...")
.edit_message_text(message.chat.id, message.id, "⏳ Подготовка архива...")
.reply_markup(get_check_keyboard(task.id.clone())) .reply_markup(get_check_keyboard(task.id.clone()))
.send() .send()
.await?; .await?;

View File

@@ -1,6 +1,5 @@
use teloxide::utils::command::BotCommands; use teloxide::utils::command::BotCommands;
#[derive(BotCommands, Clone)] #[derive(BotCommands, Clone)]
#[command(rename_rule = "lowercase")] #[command(rename_rule = "lowercase")]
pub enum HelpCommand { pub enum HelpCommand {

View File

@@ -2,11 +2,14 @@ pub mod commands;
use crate::bots::BotHandlerInternal; use crate::bots::BotHandlerInternal;
use teloxide::{prelude::*, types::ParseMode, adaptors::{Throttle, CacheMe}}; use teloxide::{
adaptors::{CacheMe, Throttle},
prelude::*,
types::ParseMode,
};
use self::commands::HelpCommand; use self::commands::HelpCommand;
pub async fn help_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal { pub async fn help_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal {
let name = message let name = message
.from() .from()

View File

@@ -1,5 +1,4 @@
use strum_macros::{EnumIter, Display}; use strum_macros::{Display, EnumIter};
#[derive(Clone, Display, EnumIter)] #[derive(Clone, Display, EnumIter)]
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]

View File

@@ -1,29 +1,32 @@
pub mod commands;
pub mod callback_data; pub mod callback_data;
pub mod commands;
use smartstring::alias::String as SmartString;
use smallvec::SmallVec; use smallvec::SmallVec;
use smartstring::alias::String as SmartString;
use teloxide::{ use teloxide::{
adaptors::{CacheMe, Throttle},
prelude::*, prelude::*,
types::{InlineKeyboardButton, InlineKeyboardMarkup}, types::{InlineKeyboardButton, InlineKeyboardMarkup},
adaptors::{Throttle, CacheMe},
}; };
use crate::bots::{ use crate::bots::{
approved_bot::{ approved_bot::{
modules::random::callback_data::RandomCallbackData,
services::{ services::{
book_library::{self, formatters::Format}, book_library::{self, formatters::Format},
user_settings::get_user_or_default_lang_codes, user_settings::get_user_or_default_lang_codes,
}, },
tools::filter_callback_query, modules::random::callback_data::RandomCallbackData, tools::filter_callback_query,
}, },
BotHandlerInternal, BotHandlerInternal,
}; };
use self::commands::RandomCommand; use self::commands::RandomCommand;
async fn random_handler(
async fn random_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> crate::bots::BotHandlerInternal { message: Message,
bot: CacheMe<Throttle<Bot>>,
) -> crate::bots::BotHandlerInternal {
const MESSAGE_TEXT: &str = "Что хотим получить?"; const MESSAGE_TEXT: &str = "Что хотим получить?";
let keyboard = InlineKeyboardMarkup { let keyboard = InlineKeyboardMarkup {
@@ -55,8 +58,7 @@ async fn random_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> crate:
], ],
}; };
bot bot.send_message(message.chat.id, MESSAGE_TEXT)
.send_message(message.chat.id, MESSAGE_TEXT)
.reply_to_message_id(message.id) .reply_to_message_id(message.id)
.reply_markup(keyboard) .reply_markup(keyboard)
.send() .send()
@@ -76,12 +78,11 @@ where
let item = match item { let item = match item {
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
bot bot.send_message(cq.from.id, "Ошибка! Попробуйте позже :(")
.send_message(cq.from.id, "Ошибка! Попробуйте позже :(")
.send() .send()
.await?; .await?;
return Err(err); return Err(err);
}, }
}; };
let item_message = item.format(4096).result; let item_message = item.format(4096).result;
@@ -89,9 +90,7 @@ where
bot.send_message(cq.from.id, item_message) bot.send_message(cq.from.id, item_message)
.reply_markup(InlineKeyboardMarkup { .reply_markup(InlineKeyboardMarkup {
inline_keyboard: vec![vec![InlineKeyboardButton { inline_keyboard: vec![vec![InlineKeyboardButton {
kind: teloxide::types::InlineKeyboardButtonKind::CallbackData( kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(cq.data.unwrap()),
cq.data.unwrap(),
),
text: String::from("Повторить?"), text: String::from("Повторить?"),
}]], }]],
}) })
@@ -107,7 +106,7 @@ where
.send() .send()
.await?; .await?;
Ok(()) Ok(())
}, }
None => Ok(()), None => Ok(()),
} }
} }
@@ -128,18 +127,20 @@ where
get_random_item_handler_internal(cq, bot, item).await get_random_item_handler_internal(cq, bot, item).await
} }
async fn get_genre_metas_handler(cq: CallbackQuery, bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal { async fn get_genre_metas_handler(
cq: CallbackQuery,
bot: CacheMe<Throttle<Bot>>,
) -> BotHandlerInternal {
let genre_metas = book_library::get_genre_metas().await?; let genre_metas = book_library::get_genre_metas().await?;
let message = match cq.message { let message = match cq.message {
Some(v) => v, Some(v) => v,
None => { None => {
bot bot.send_message(cq.from.id, "Ошибка! Начните заново :(")
.send_message(cq.from.id, "Ошибка! Начните заново :(")
.send() .send()
.await?; .await?;
return Ok(()); return Ok(());
}, }
}; };
let keyboard = InlineKeyboardMarkup { let keyboard = InlineKeyboardMarkup {
@@ -160,8 +161,7 @@ async fn get_genre_metas_handler(cq: CallbackQuery, bot: CacheMe<Throttle<Bot>>)
.collect(), .collect(),
}; };
bot bot.edit_message_reply_markup(message.chat.id, message.id)
.edit_message_reply_markup(message.chat.id, message.id)
.reply_markup(keyboard) .reply_markup(keyboard)
.send() .send()
.await?; .await?;
@@ -179,8 +179,7 @@ async fn get_genres_by_meta_handler(
let meta = match genre_metas.get(genre_index as usize) { let meta = match genre_metas.get(genre_index as usize) {
Some(v) => v, Some(v) => v,
None => { None => {
bot bot.send_message(cq.from.id, "Ошибка! Попробуйте позже :(")
.send_message(cq.from.id, "Ошибка! Попробуйте позже :(")
.send() .send()
.await?; .await?;
@@ -188,7 +187,8 @@ async fn get_genres_by_meta_handler(
} }
}; };
let mut buttons: Vec<Vec<InlineKeyboardButton>> = book_library::get_genres(meta.into()).await? let mut buttons: Vec<Vec<InlineKeyboardButton>> = book_library::get_genres(meta.into())
.await?
.items .items
.into_iter() .into_iter()
.map(|genre| { .map(|genre| {
@@ -217,8 +217,7 @@ async fn get_genres_by_meta_handler(
let message = match cq.message { let message = match cq.message {
Some(message) => message, Some(message) => message,
None => { None => {
bot bot.send_message(cq.from.id, "Ошибка! Начните заново :(")
.send_message(cq.from.id, "Ошибка! Начните заново :(")
.send() .send()
.await?; .await?;
@@ -226,8 +225,7 @@ async fn get_genres_by_meta_handler(
} }
}; };
bot bot.edit_message_reply_markup(message.chat.id, message.id)
.edit_message_reply_markup(message.chat.id, message.id)
.reply_markup(keyboard) .reply_markup(keyboard)
.send() .send()
.await?; .await?;
@@ -249,30 +247,46 @@ async fn get_random_book_by_genre(
pub fn get_random_handler() -> crate::bots::BotHandler { pub fn get_random_handler() -> crate::bots::BotHandler {
dptree::entry() dptree::entry()
.branch( .branch(Update::filter_message().branch(
Update::filter_message() dptree::entry().filter_command::<RandomCommand>().endpoint(
.branch( |message, command, bot| async {
dptree::entry()
.filter_command::<RandomCommand>()
.endpoint(|message, command, bot| async {
match command { match command {
RandomCommand::Random => random_handler(message, bot).await, RandomCommand::Random => random_handler(message, bot).await,
} }
}) },
) ),
) ))
.branch( .branch(
Update::filter_callback_query() Update::filter_callback_query()
.chain(filter_callback_query::<RandomCallbackData>()) .chain(filter_callback_query::<RandomCallbackData>())
.endpoint(|cq: CallbackQuery, callback_data: RandomCallbackData, bot: CacheMe<Throttle<Bot>>| async move { .endpoint(
|cq: CallbackQuery,
callback_data: RandomCallbackData,
bot: CacheMe<Throttle<Bot>>| async move {
match callback_data { match callback_data {
RandomCallbackData::RandomBook => get_random_item_handler(cq, bot, book_library::get_random_book).await, RandomCallbackData::RandomBook => {
RandomCallbackData::RandomAuthor => get_random_item_handler(cq, bot, book_library::get_random_author).await, get_random_item_handler(cq, bot, book_library::get_random_book)
RandomCallbackData::RandomSequence => get_random_item_handler(cq, bot, book_library::get_random_sequence).await, .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,
} }
}) 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
}
}
},
),
) )
} }

View File

@@ -5,7 +5,6 @@ use strum_macros::EnumIter;
use crate::bots::approved_bot::modules::utils::pagination::GetPaginationCallbackData; use crate::bots::approved_bot::modules::utils::pagination::GetPaginationCallbackData;
#[derive(Clone, EnumIter)] #[derive(Clone, EnumIter)]
pub enum SearchCallbackData { pub enum SearchCallbackData {
Book { page: u32 }, Book { page: u32 },
@@ -56,12 +55,8 @@ impl FromStr for SearchCallbackData {
impl GetPaginationCallbackData for SearchCallbackData { impl GetPaginationCallbackData for SearchCallbackData {
fn get_pagination_callback_data(&self, target_page: u32) -> String { fn get_pagination_callback_data(&self, target_page: u32) -> String {
match self { match self {
SearchCallbackData::Book { .. } => { SearchCallbackData::Book { .. } => SearchCallbackData::Book { page: target_page },
SearchCallbackData::Book { page: target_page } SearchCallbackData::Authors { .. } => SearchCallbackData::Authors { page: target_page },
}
SearchCallbackData::Authors { .. } => {
SearchCallbackData::Authors { page: target_page }
}
SearchCallbackData::Sequences { .. } => { SearchCallbackData::Sequences { .. } => {
SearchCallbackData::Sequences { page: target_page } SearchCallbackData::Sequences { page: target_page }
} }

View File

@@ -6,15 +6,18 @@ use smartstring::alias::String as SmartString;
use smallvec::SmallVec; use smallvec::SmallVec;
use teloxide::{ use teloxide::{
adaptors::{CacheMe, Throttle},
dispatching::dialogue::GetChatId,
prelude::*, prelude::*,
types::{InlineKeyboardButton, InlineKeyboardMarkup}, dispatching::dialogue::GetChatId, adaptors::{Throttle, CacheMe}, types::{InlineKeyboardButton, InlineKeyboardMarkup},
}; };
use crate::bots::{ use crate::bots::{
approved_bot::{ approved_bot::{
services::{ services::{
book_library::{ book_library::{
formatters::{Format, FormatTitle}, search_author, search_book, search_sequence, search_translator, formatters::{Format, FormatTitle},
search_author, search_book, search_sequence, search_translator,
types::Page, types::Page,
}, },
user_settings::get_user_or_default_lang_codes, user_settings::get_user_or_default_lang_codes,
@@ -28,7 +31,6 @@ use self::{callback_data::SearchCallbackData, utils::get_query};
use super::utils::pagination::generic_get_pagination_keyboard; use super::utils::pagination::generic_get_pagination_keyboard;
async fn generic_search_pagination_handler<T, P, Fut>( async fn generic_search_pagination_handler<T, P, Fut>(
cq: CallbackQuery, cq: CallbackQuery,
bot: CacheMe<Throttle<Bot>>, bot: CacheMe<Throttle<Bot>>,
@@ -46,11 +48,11 @@ where
let query = get_query(cq); let query = get_query(cq);
let (chat_id, query, message_id) = match (chat_id, query, message_id) { let (chat_id, query, message_id) = match (chat_id, query, message_id) {
(Some(chat_id), Some(query), Some(message_id)) => { (Some(chat_id), Some(query), Some(message_id)) => (chat_id, query, message_id),
(chat_id, query, message_id)
}
(Some(chat_id), _, _) => { (Some(chat_id), _, _) => {
bot.send_message(chat_id, "Повторите поиск сначала").send().await?; bot.send_message(chat_id, "Повторите поиск сначала")
.send()
.await?;
return Ok(()); return Ok(());
} }
_ => { _ => {
@@ -70,8 +72,7 @@ where
let mut items_page = match items_getter(query.clone(), page, allowed_langs.clone()).await { let mut items_page = match items_getter(query.clone(), page, allowed_langs.clone()).await {
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
bot bot.send_message(chat_id, "Ошибка! Попробуйте позже :(")
.send_message(chat_id, "Ошибка! Попробуйте позже :(")
.send() .send()
.await?; .await?;
@@ -92,17 +93,11 @@ where
}; };
if page > items_page.pages { if page > items_page.pages {
items_page = match items_getter( items_page =
query.clone(), match items_getter(query.clone(), items_page.pages, allowed_langs.clone()).await {
items_page.pages,
allowed_langs.clone(),
)
.await
{
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
bot bot.send_message(chat_id, "Ошибка! Попробуйте позже :(")
.send_message(chat_id, "Ошибка! Попробуйте позже :(")
.send() .send()
.await?; .await?;
@@ -115,8 +110,7 @@ where
let keyboard = generic_get_pagination_keyboard(page, items_page.pages, search_data, true); let keyboard = generic_get_pagination_keyboard(page, items_page.pages, search_data, true);
bot bot.edit_message_text(chat_id, message_id, formated_page)
.edit_message_text(chat_id, message_id, formated_page)
.reply_markup(keyboard) .reply_markup(keyboard)
.send() .send()
.await?; .await?;
@@ -156,8 +150,7 @@ pub async fn message_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> B
], ],
}; };
bot bot.send_message(message.chat.id, message_text)
.send_message(message.chat.id, message_text)
.reply_to_message_id(message.id) .reply_to_message_id(message.id)
.reply_markup(keyboard) .reply_markup(keyboard)
.send() .send()
@@ -167,19 +160,57 @@ pub async fn message_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> B
} }
pub fn get_search_handler() -> crate::bots::BotHandler { pub fn get_search_handler() -> crate::bots::BotHandler {
dptree::entry().branch( dptree::entry()
.branch(
Update::filter_message() Update::filter_message()
.endpoint(|message, bot| async move { message_handler(message, bot).await }), .endpoint(|message, bot| async move { message_handler(message, bot).await }),
).branch( )
.branch(
Update::filter_callback_query() Update::filter_callback_query()
.chain(filter_callback_query::<SearchCallbackData>()) .chain(filter_callback_query::<SearchCallbackData>())
.endpoint(|cq: CallbackQuery, callback_data: SearchCallbackData, bot: CacheMe<Throttle<Bot>>| async move { .endpoint(
|cq: CallbackQuery,
callback_data: SearchCallbackData,
bot: CacheMe<Throttle<Bot>>| async move {
match callback_data { match callback_data {
SearchCallbackData::Book { .. } => generic_search_pagination_handler(cq, bot, callback_data, search_book).await, SearchCallbackData::Book { .. } => {
SearchCallbackData::Authors { .. } => generic_search_pagination_handler(cq, bot, callback_data, search_author).await, generic_search_pagination_handler(
SearchCallbackData::Sequences { .. } => generic_search_pagination_handler(cq, bot, callback_data, search_sequence).await, cq,
SearchCallbackData::Translators { .. } => generic_search_pagination_handler(cq, bot, callback_data, search_translator).await, bot,
callback_data,
search_book,
)
.await
} }
}) SearchCallbackData::Authors { .. } => {
generic_search_pagination_handler(
cq,
bot,
callback_data,
search_author,
)
.await
}
SearchCallbackData::Sequences { .. } => {
generic_search_pagination_handler(
cq,
bot,
callback_data,
search_sequence,
)
.await
}
SearchCallbackData::Translators { .. } => {
generic_search_pagination_handler(
cq,
bot,
callback_data,
search_translator,
)
.await
}
}
},
),
) )
} }

View File

@@ -1,6 +1,5 @@
use teloxide::types::CallbackQuery; use teloxide::types::CallbackQuery;
pub fn get_query(cq: CallbackQuery) -> Option<String> { pub fn get_query(cq: CallbackQuery) -> Option<String> {
cq.message cq.message
.map(|message| { .map(|message| {

View File

@@ -3,7 +3,6 @@ use std::str::FromStr;
use regex::Regex; use regex::Regex;
use smartstring::alias::String as SmartString; use smartstring::alias::String as SmartString;
#[derive(Clone)] #[derive(Clone)]
pub enum SettingsCallbackData { pub enum SettingsCallbackData {
Settings, Settings,

View File

@@ -1,6 +1,5 @@
use teloxide::macros::BotCommands; use teloxide::macros::BotCommands;
#[derive(BotCommands, Clone)] #[derive(BotCommands, Clone)]
#[command(rename_rule = "lowercase")] #[command(rename_rule = "lowercase")]
pub enum SettingsCommand { pub enum SettingsCommand {

View File

@@ -1,5 +1,5 @@
pub mod commands;
pub mod callback_data; pub mod callback_data;
pub mod commands;
use std::collections::HashSet; use std::collections::HashSet;
@@ -16,12 +16,12 @@ use crate::bots::{
}; };
use teloxide::{ use teloxide::{
adaptors::{CacheMe, Throttle},
prelude::*, prelude::*,
types::{InlineKeyboardButton, InlineKeyboardMarkup, Me}, adaptors::{Throttle, CacheMe}, types::{InlineKeyboardButton, InlineKeyboardMarkup, Me},
}; };
use self::{commands::SettingsCommand, callback_data::SettingsCallbackData}; use self::{callback_data::SettingsCallbackData, commands::SettingsCommand};
async fn settings_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal { async fn settings_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal {
let keyboard = InlineKeyboardMarkup { let keyboard = InlineKeyboardMarkup {
@@ -33,8 +33,7 @@ async fn settings_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotH
}]], }]],
}; };
bot bot.send_message(message.chat.id, "Настройки")
.send_message(message.chat.id, "Настройки")
.reply_markup(keyboard) .reply_markup(keyboard)
.send() .send()
.await?; .await?;
@@ -42,7 +41,10 @@ async fn settings_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotH
Ok(()) Ok(())
} }
fn get_lang_keyboard(all_langs: Vec<Lang>, allowed_langs: HashSet<SmartString>) -> InlineKeyboardMarkup { fn get_lang_keyboard(
all_langs: Vec<Lang>,
allowed_langs: HashSet<SmartString>,
) -> InlineKeyboardMarkup {
let buttons = all_langs let buttons = all_langs
.into_iter() .into_iter()
.map(|lang| { .map(|lang| {
@@ -78,9 +80,11 @@ async fn settings_callback_handler(
let message = match cq.message { let message = match cq.message {
Some(v) => v, Some(v) => v,
None => { None => {
bot.send_message(cq.from.id, "Ошибка! Попробуйте заново(").send().await?; bot.send_message(cq.from.id, "Ошибка! Попробуйте заново(")
.send()
.await?;
return Ok(()); return Ok(());
}, }
}; };
let user = cq.from; let user = cq.from;
@@ -103,14 +107,13 @@ async fn settings_callback_handler(
}; };
if allowed_langs_set.is_empty() { if allowed_langs_set.is_empty() {
bot bot.answer_callback_query(cq.id)
.answer_callback_query(cq.id)
.text("Должен быть активен, хотя бы один язык!") .text("Должен быть активен, хотя бы один язык!")
.show_alert(true) .show_alert(true)
.send() .send()
.await?; .await?;
return Ok(()) return Ok(());
} }
if let Err(err) = create_or_update_user_settings( if let Err(err) = create_or_update_user_settings(
@@ -121,23 +124,27 @@ async fn settings_callback_handler(
me.username.clone().unwrap(), me.username.clone().unwrap(),
allowed_langs_set.clone().into_iter().collect(), allowed_langs_set.clone().into_iter().collect(),
) )
.await { .await
bot.send_message(message.chat.id, "Ошибка! Попробуйте заново(").send().await?; {
bot.send_message(message.chat.id, "Ошибка! Попробуйте заново(")
.send()
.await?;
return Err(err); return Err(err);
} }
let all_langs = match get_langs().await { let all_langs = match get_langs().await {
Ok(v) => v, Ok(v) => v,
Err(err) => { Err(err) => {
bot.send_message(message.chat.id, "Ошибка! Попробуйте заново(").send().await?; bot.send_message(message.chat.id, "Ошибка! Попробуйте заново(")
return Err(err) .send()
}, .await?;
return Err(err);
}
}; };
let keyboard = get_lang_keyboard(all_langs, allowed_langs_set); let keyboard = get_lang_keyboard(all_langs, allowed_langs_set);
bot bot.edit_message_reply_markup(message.chat.id, message.id)
.edit_message_reply_markup(message.chat.id, message.id)
.reply_markup(keyboard) .reply_markup(keyboard)
.send() .send()
.await?; .await?;

View File

@@ -1,30 +1,37 @@
use crate::bots::BotHandlerInternal; use crate::bots::BotHandlerInternal;
use teloxide::{ use teloxide::{
adaptors::{CacheMe, Throttle},
prelude::*, prelude::*,
utils::command::BotCommands, adaptors::{Throttle, CacheMe}, utils::command::BotCommands,
}; };
#[derive(BotCommands, Clone)] #[derive(BotCommands, Clone)]
#[command(rename_rule = "lowercase")] #[command(rename_rule = "lowercase")]
enum SupportCommand { enum SupportCommand {
Support, Support,
Donate Donate,
} }
pub async fn support_command_handler( pub async fn support_command_handler(
message: Message, message: Message,
bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal { bot: CacheMe<Throttle<Bot>>,
) -> BotHandlerInternal {
let is_bot = message.from().unwrap().is_bot; let is_bot = message.from().unwrap().is_bot;
let username = if is_bot { let username = if is_bot {
&message.reply_to_message().unwrap().from().unwrap().first_name &message
.reply_to_message()
.unwrap()
.from()
.unwrap()
.first_name
} else { } else {
&message.from().unwrap().first_name &message.from().unwrap().first_name
}; };
let message_text = format!(" let message_text = format!(
"
Привет, {username}! Привет, {username}!
Этот бот существует благодаря пожертвованиям от наших пользователей. Этот бот существует благодаря пожертвованиям от наших пользователей.
@@ -50,10 +57,10 @@ Bitcoin - BTC:
The Open Network - TON: The Open Network - TON:
<pre>UQA4MySrq_60b_VMlR6UEmc_0u-neAUTXdtv8oKr_i6uhQNd</pre> <pre>UQA4MySrq_60b_VMlR6UEmc_0u-neAUTXdtv8oKr_i6uhQNd</pre>
"); "
);
bot bot.send_message(message.chat.id, message_text)
.send_message(message.chat.id, message_text)
.parse_mode(teloxide::types::ParseMode::Html) .parse_mode(teloxide::types::ParseMode::Html)
.disable_web_page_preview(true) .disable_web_page_preview(true)
.await?; .await?;
@@ -64,7 +71,9 @@ The Open Network - TON:
pub fn get_support_handler() -> crate::bots::BotHandler { pub fn get_support_handler() -> crate::bots::BotHandler {
dptree::entry().branch( dptree::entry().branch(
Update::filter_message().branch( Update::filter_message().branch(
dptree::entry().filter_command::<SupportCommand>().endpoint(support_command_handler), dptree::entry()
.filter_command::<SupportCommand>()
.endpoint(support_command_handler),
), ),
) )
} }

View File

@@ -6,7 +6,6 @@ use regex::Regex;
use crate::bots::approved_bot::modules::utils::pagination::GetPaginationCallbackData; use crate::bots::approved_bot::modules::utils::pagination::GetPaginationCallbackData;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct UpdateLogCallbackData { pub struct UpdateLogCallbackData {
pub from: NaiveDate, pub from: NaiveDate,

View File

@@ -1,6 +1,5 @@
use teloxide::macros::BotCommands; use teloxide::macros::BotCommands;
#[derive(BotCommands, Clone)] #[derive(BotCommands, Clone)]
#[command(rename_rule = "snake_case")] #[command(rename_rule = "snake_case")]
pub enum UpdateLogCommand { pub enum UpdateLogCommand {

View File

@@ -1,5 +1,5 @@
pub mod commands;
pub mod callback_data; pub mod callback_data;
pub mod commands;
use chrono::{prelude::*, Duration}; use chrono::{prelude::*, Duration};
@@ -9,16 +9,15 @@ use crate::bots::{
}; };
use teloxide::{ use teloxide::{
adaptors::{CacheMe, Throttle},
prelude::*, prelude::*,
types::{InlineKeyboardButton, InlineKeyboardMarkup}, types::{InlineKeyboardButton, InlineKeyboardMarkup},
adaptors::{Throttle, CacheMe},
}; };
use self::{commands::UpdateLogCommand, callback_data::UpdateLogCallbackData}; use self::{callback_data::UpdateLogCallbackData, commands::UpdateLogCommand};
use super::utils::pagination::generic_get_pagination_keyboard; use super::utils::pagination::generic_get_pagination_keyboard;
async fn update_log_command(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal { async fn update_log_command(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal {
let now = Utc::now().date_naive(); let now = Utc::now().date_naive();
let d3 = now - Duration::days(3); let d3 = now - Duration::days(3);
@@ -82,9 +81,11 @@ async fn update_log_pagination_handler(
let message = match cq.message { let message = match cq.message {
Some(v) => v, Some(v) => v,
None => { None => {
bot.send_message(cq.from.id, "Ошибка! Попробуйте заново(").send().await?; bot.send_message(cq.from.id, "Ошибка! Попробуйте заново(")
return Ok(()) .send()
}, .await?;
return Ok(());
}
}; };
let from = update_callback_data.from.format("%d.%m.%Y"); let from = update_callback_data.from.format("%d.%m.%Y");
@@ -94,14 +95,21 @@ async fn update_log_pagination_handler(
let mut items_page = get_uploaded_books( let mut items_page = get_uploaded_books(
update_callback_data.page, update_callback_data.page,
update_callback_data.from.format("%Y-%m-%d").to_string().into(), update_callback_data
update_callback_data.to.format("%Y-%m-%d").to_string().into(), .from
.format("%Y-%m-%d")
.to_string()
.into(),
update_callback_data
.to
.format("%Y-%m-%d")
.to_string()
.into(),
) )
.await?; .await?;
if items_page.pages == 0 { if items_page.pages == 0 {
bot bot.send_message(message.chat.id, "Нет новых книг за этот период.")
.send_message(message.chat.id, "Нет новых книг за этот период.")
.send() .send()
.await?; .await?;
return Ok(()); return Ok(());
@@ -110,9 +118,18 @@ async fn update_log_pagination_handler(
if update_callback_data.page > items_page.pages { if update_callback_data.page > items_page.pages {
items_page = get_uploaded_books( items_page = get_uploaded_books(
items_page.pages, items_page.pages,
update_callback_data.from.format("%Y-%m-%d").to_string().into(), update_callback_data
update_callback_data.to.format("%Y-%m-%d").to_string().into(), .from
).await?; .format("%Y-%m-%d")
.to_string()
.into(),
update_callback_data
.to
.format("%Y-%m-%d")
.to_string()
.into(),
)
.await?;
} }
let page = update_callback_data.page; let page = update_callback_data.page;
@@ -123,8 +140,7 @@ async fn update_log_pagination_handler(
let message_text = format!("{header}{formatted_page}"); let message_text = format!("{header}{formatted_page}");
let keyboard = generic_get_pagination_keyboard(page, total_pages, update_callback_data, true); let keyboard = generic_get_pagination_keyboard(page, total_pages, update_callback_data, true);
bot bot.edit_message_text(message.chat.id, message.id, message_text)
.edit_message_text(message.chat.id, message.id, message_text)
.reply_markup(keyboard) .reply_markup(keyboard)
.send() .send()
.await?; .await?;

View File

@@ -1,6 +1,5 @@
use std::fmt; use std::fmt;
#[derive(Debug)] #[derive(Debug)]
pub struct CallbackQueryParseError; pub struct CallbackQueryParseError;
@@ -12,7 +11,6 @@ impl fmt::Display for CallbackQueryParseError {
impl std::error::Error for CallbackQueryParseError {} impl std::error::Error for CallbackQueryParseError {}
#[derive(Debug)] #[derive(Debug)]
pub struct CommandParseError; pub struct CommandParseError;

View File

@@ -2,12 +2,10 @@ use teloxide::{dptree, prelude::*, types::*};
use super::errors::CommandParseError; use super::errors::CommandParseError;
pub trait CommandParse<T> { pub trait CommandParse<T> {
fn parse(s: &str, bot_name: &str) -> Result<T, CommandParseError>; fn parse(s: &str, bot_name: &str) -> Result<T, CommandParseError>;
} }
pub fn filter_command<Output>() -> crate::bots::BotHandler pub fn filter_command<Output>() -> crate::bots::BotHandler
where where
Output: CommandParse<Output> + Send + Sync + 'static, Output: CommandParse<Output> + Send + Sync + 'static,

View File

@@ -1,4 +1,4 @@
pub mod errors;
pub mod filter_command;
pub mod pagination; pub mod pagination;
pub mod split_text; pub mod split_text;
pub mod filter_command;
pub mod errors;

View File

@@ -1,6 +1,5 @@
use teloxide::types::{InlineKeyboardButton, InlineKeyboardMarkup}; use teloxide::types::{InlineKeyboardButton, InlineKeyboardMarkup};
pub enum PaginationDelta { pub enum PaginationDelta {
OneMinus, OneMinus,
OnePlus, OnePlus,
@@ -12,7 +11,6 @@ pub trait GetPaginationCallbackData {
fn get_pagination_callback_data(&self, target_page: u32) -> String; fn get_pagination_callback_data(&self, target_page: u32) -> String;
} }
pub fn generic_get_pagination_button<T>( pub fn generic_get_pagination_button<T>(
target_page: u32, target_page: u32,
delta: PaginationDelta, delta: PaginationDelta,
@@ -36,7 +34,6 @@ where
} }
} }
pub fn generic_get_pagination_keyboard<T>( pub fn generic_get_pagination_keyboard<T>(
page: u32, page: u32,
total_pages: u32, total_pages: u32,

View File

@@ -26,7 +26,6 @@ pub fn split_text_to_chunks(text: &str, width: usize) -> Vec<String> {
result result
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::bots::approved_bot::modules::utils::split_text::split_text_to_chunks; use crate::bots::approved_bot::modules::utils::split_text::split_text_to_chunks;

View File

@@ -19,7 +19,7 @@ pub enum TaskStatus {
InProgress, InProgress,
Archiving, Archiving,
Complete, Complete,
Failed Failed,
} }
#[derive(Serialize)] #[derive(Serialize)]
@@ -38,7 +38,7 @@ pub struct Task {
pub error_message: Option<String>, pub error_message: Option<String>,
pub result_filename: Option<String>, pub result_filename: Option<String>,
pub result_link: Option<String>, pub result_link: Option<String>,
pub content_size: Option<u64> pub content_size: Option<u64>,
} }
pub async fn create_task( pub async fn create_task(

View File

@@ -2,13 +2,12 @@ use base64::{engine::general_purpose, Engine};
use reqwest::StatusCode; use reqwest::StatusCode;
use std::fmt; use std::fmt;
use crate::{config, bots::approved_bot::modules::download::callback_data::DownloadQueryData}; use crate::{bots::approved_bot::modules::download::callback_data::DownloadQueryData, config};
use self::types::{CachedMessage, DownloadFile, DownloadLink}; use self::types::{CachedMessage, DownloadFile, DownloadLink};
pub mod types; pub mod types;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct DownloadError { struct DownloadError {
status_code: StatusCode, status_code: StatusCode,
@@ -25,7 +24,10 @@ impl std::error::Error for DownloadError {}
pub async fn get_cached_message( pub async fn get_cached_message(
download_data: &DownloadQueryData, download_data: &DownloadQueryData,
) -> Result<CachedMessage, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<CachedMessage, Box<dyn std::error::Error + Send + Sync>> {
let DownloadQueryData::DownloadData { book_id: id, file_type: format } = download_data; let DownloadQueryData::DownloadData {
book_id: id,
file_type: format,
} = download_data;
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let response = client let response = client
@@ -48,9 +50,12 @@ pub async fn get_cached_message(
} }
pub async fn get_download_link( pub async fn get_download_link(
download_data: &DownloadQueryData download_data: &DownloadQueryData,
) -> Result<DownloadLink, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<DownloadLink, Box<dyn std::error::Error + Send + Sync>> {
let DownloadQueryData::DownloadData { book_id: id, file_type: format } = download_data; let DownloadQueryData::DownloadData {
book_id: id,
file_type: format,
} = download_data;
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let response = client let response = client
@@ -75,7 +80,10 @@ pub async fn get_download_link(
pub async fn download_file( pub async fn download_file(
download_data: &DownloadQueryData, download_data: &DownloadQueryData,
) -> Result<DownloadFile, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<DownloadFile, Box<dyn std::error::Error + Send + Sync>> {
let DownloadQueryData::DownloadData { book_id: id, file_type: format } = download_data; let DownloadQueryData::DownloadData {
book_id: id,
file_type: format,
} = download_data;
let response = reqwest::Client::new() let response = reqwest::Client::new()
.get(format!( .get(format!(
@@ -120,10 +128,9 @@ pub async fn download_file(
}) })
} }
pub async fn download_file_by_link( pub async fn download_file_by_link(
filename: String, filename: String,
link: String link: String,
) -> Result<DownloadFile, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<DownloadFile, Box<dyn std::error::Error + Send + Sync>> {
let response = reqwest::Client::new() let response = reqwest::Client::new()
.get(link) .get(link)

View File

@@ -1,6 +1,5 @@
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct CachedMessage { pub struct CachedMessage {
pub message_id: i32, pub message_id: i32,
@@ -15,5 +14,5 @@ pub struct DownloadFile {
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct DownloadLink { pub struct DownloadLink {
pub link: String pub link: String,
} }

View File

@@ -1,10 +1,12 @@
use std::cmp::min; use std::cmp::min;
use crate::bots::approved_bot::modules::download::commands::{StartDownloadCommand, DownloadArchiveCommand}; use crate::bots::approved_bot::modules::download::commands::{
DownloadArchiveCommand, StartDownloadCommand,
};
use super::types::{ use super::types::{
Author, AuthorBook, Book, BookAuthor, BookGenre, SearchBook, Sequence, Translator, Author, AuthorBook, Book, BookAuthor, BookGenre, BookTranslator, Empty, SearchBook, Sequence,
TranslatorBook, SequenceBook, BookTranslator, Empty, SequenceBook, Translator, TranslatorBook,
}; };
const NO_LIMIT: usize = 4096; const NO_LIMIT: usize = 4096;
@@ -45,7 +47,7 @@ impl FormatTitle for BookAuthor {
} = self; } = self;
if *id == 0 { if *id == 0 {
return "".to_string() return "".to_string();
} }
let command = (DownloadArchiveCommand::Author { id: *id }).to_string(); let command = (DownloadArchiveCommand::Author { id: *id }).to_string();
@@ -64,7 +66,7 @@ impl FormatTitle for BookTranslator {
} = self; } = self;
if *id == 0 { if *id == 0 {
return "".to_string() return "".to_string();
} }
let command = (DownloadArchiveCommand::Translator { id: *id }).to_string(); let command = (DownloadArchiveCommand::Translator { id: *id }).to_string();
@@ -78,7 +80,7 @@ impl FormatTitle for Sequence {
let Sequence { id, name } = self; let Sequence { id, name } = self;
if *id == 0 { if *id == 0 {
return "".to_string() return "".to_string();
} }
let command = (DownloadArchiveCommand::Sequence { id: *id }).to_string(); let command = (DownloadArchiveCommand::Sequence { id: *id }).to_string();
@@ -115,7 +117,7 @@ impl FormatInline for BookTranslator {
fn format_authors(authors: Vec<BookAuthor>, count: usize) -> String { fn format_authors(authors: Vec<BookAuthor>, count: usize) -> String {
if count == 0 { if count == 0 {
return "".to_string() return "".to_string();
} }
match !authors.is_empty() { match !authors.is_empty() {
@@ -126,7 +128,11 @@ fn format_authors(authors: Vec<BookAuthor>, count: usize) -> String {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n"); .join("\n");
let post_fix = if authors.len() > count { "\nи др." } else { "" }; let post_fix = if authors.len() > count {
"\nи др."
} else {
""
};
format!("Авторы:\n{formated_authors}{post_fix}\n") format!("Авторы:\n{formated_authors}{post_fix}\n")
} }
false => "".to_string(), false => "".to_string(),
@@ -135,7 +141,7 @@ fn format_authors(authors: Vec<BookAuthor>, count: usize) -> String {
fn format_translators(translators: Vec<BookTranslator>, count: usize) -> String { fn format_translators(translators: Vec<BookTranslator>, count: usize) -> String {
if count == 0 { if count == 0 {
return "".to_string() return "".to_string();
} }
match !translators.is_empty() { match !translators.is_empty() {
@@ -146,7 +152,11 @@ fn format_translators(translators: Vec<BookTranslator>, count: usize) -> String
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n"); .join("\n");
let post_fix = if translators.len() > count { "\nи др." } else { "" }; let post_fix = if translators.len() > count {
"\nи др."
} else {
""
};
format!("Переводчики:\n{formated_translators}{post_fix}\n") format!("Переводчики:\n{formated_translators}{post_fix}\n")
} }
false => "".to_string(), false => "".to_string(),
@@ -155,7 +165,7 @@ fn format_translators(translators: Vec<BookTranslator>, count: usize) -> String
fn format_sequences(sequences: Vec<Sequence>, count: usize) -> String { fn format_sequences(sequences: Vec<Sequence>, count: usize) -> String {
if count == 0 { if count == 0 {
return "".to_string() return "".to_string();
} }
match !sequences.is_empty() { match !sequences.is_empty() {
@@ -166,7 +176,11 @@ fn format_sequences(sequences: Vec<Sequence>, count: usize) -> String {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n"); .join("\n");
let post_fix = if sequences.len() > count { "\nи др." } else { "" }; let post_fix = if sequences.len() > count {
"\nи др."
} else {
""
};
format!("Серии:\n{formated_sequences}{post_fix}\n") format!("Серии:\n{formated_sequences}{post_fix}\n")
} }
false => "".to_string(), false => "".to_string(),
@@ -175,7 +189,7 @@ fn format_sequences(sequences: Vec<Sequence>, count: usize) -> String {
fn format_genres(genres: Vec<BookGenre>, count: usize) -> String { fn format_genres(genres: Vec<BookGenre>, count: usize) -> String {
if count == 0 { if count == 0 {
return "".to_string() return "".to_string();
} }
match !genres.is_empty() { match !genres.is_empty() {
@@ -186,7 +200,11 @@ fn format_genres(genres: Vec<BookGenre>, count: usize) -> String {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n"); .join("\n");
let post_fix = if genres.len() > count { "\nи др." } else { "" }; let post_fix = if genres.len() > count {
"\nи др."
} else {
""
};
format!("Жанры:\n{formated_genres}{post_fix}\n") format!("Жанры:\n{formated_genres}{post_fix}\n")
} }
false => "".to_string(), false => "".to_string(),
@@ -216,7 +234,7 @@ impl Format for Author {
FormatResult { FormatResult {
result, result,
current_size: result_len, current_size: result_len,
max_size: result_len max_size: result_len,
} }
} }
} }
@@ -234,7 +252,7 @@ impl Format for Sequence {
FormatResult { FormatResult {
result, result,
current_size: result_len, current_size: result_len,
max_size: result_len max_size: result_len,
} }
} }
} }
@@ -262,7 +280,7 @@ impl Format for Translator {
FormatResult { FormatResult {
result, result,
current_size: result_len, current_size: result_len,
max_size: result_len max_size: result_len,
} }
} }
} }
@@ -284,7 +302,12 @@ impl FormatVectorsCounts {
} }
fn sub(self) -> Self { fn sub(self) -> Self {
let Self {mut authors, mut translators, mut sequences, mut genres} = self; let Self {
mut authors,
mut translators,
mut sequences,
mut genres,
} = self;
if genres > 0 { if genres > 0 {
genres -= 1; genres -= 1;
@@ -293,8 +316,8 @@ impl FormatVectorsCounts {
authors, authors,
translators, translators,
sequences, sequences,
genres genres,
} };
} }
if sequences > 0 { if sequences > 0 {
@@ -304,8 +327,8 @@ impl FormatVectorsCounts {
authors, authors,
translators, translators,
sequences, sequences,
genres genres,
} };
} }
if translators > 0 { if translators > 0 {
@@ -315,8 +338,8 @@ impl FormatVectorsCounts {
authors, authors,
translators, translators,
sequences, sequences,
genres genres,
} };
} }
if authors > 0 { if authors > 0 {
@@ -326,15 +349,15 @@ impl FormatVectorsCounts {
authors, authors,
translators, translators,
sequences, sequences,
genres genres,
} };
} }
Self { Self {
authors, authors,
translators, translators,
sequences, sequences,
genres genres,
} }
} }
} }
@@ -354,14 +377,20 @@ impl FormatVectorsResult {
} }
fn with_max_result_size(self, max_result_size: usize) -> Self { fn with_max_result_size(self, max_result_size: usize) -> Self {
let Self { authors, translators, sequences, genres, .. } = self; let Self {
authors,
translators,
sequences,
genres,
..
} = self;
Self { Self {
authors, authors,
translators, translators,
sequences, sequences,
genres, genres,
max_result_size max_result_size,
} }
} }
} }
@@ -372,7 +401,7 @@ impl Book {
authors: self.authors.len(), authors: self.authors.len(),
translators: self.translators.len(), translators: self.translators.len(),
sequences: self.sequences.len(), sequences: self.sequences.len(),
genres: self.genres.len() genres: self.genres.len(),
}; };
let mut result = FormatVectorsResult { let mut result = FormatVectorsResult {
@@ -380,7 +409,7 @@ impl Book {
translators: format_translators(self.translators.clone(), counts.translators), translators: format_translators(self.translators.clone(), counts.translators),
sequences: format_sequences(self.sequences.clone(), counts.sequences), sequences: format_sequences(self.sequences.clone(), counts.sequences),
genres: format_genres(self.genres.clone(), counts.genres), genres: format_genres(self.genres.clone(), counts.genres),
max_result_size: 0 max_result_size: 0,
}; };
let max_result_size = result.len(); let max_result_size = result.len();
@@ -393,7 +422,7 @@ impl Book {
translators: format_translators(self.translators.clone(), counts.translators), translators: format_translators(self.translators.clone(), counts.translators),
sequences: format_sequences(self.sequences.clone(), counts.sequences), sequences: format_sequences(self.sequences.clone(), counts.sequences),
genres: format_genres(self.genres.clone(), counts.genres), genres: format_genres(self.genres.clone(), counts.genres),
max_result_size: 0 max_result_size: 0,
}; };
} }
@@ -426,17 +455,23 @@ impl Format for Book {
let download_links = format!("Скачать:\n📥{download_command}"); let download_links = format!("Скачать:\n📥{download_command}");
let required_data_len: usize = format!("{book_title}{annotations}{download_links}").len(); let required_data_len: usize = format!("{book_title}{annotations}{download_links}").len();
let FormatVectorsResult { authors, translators, sequences, genres, max_result_size } = self.format_vectors( let FormatVectorsResult {
max_size - required_data_len authors,
); translators,
sequences,
genres,
max_result_size,
} = self.format_vectors(max_size - required_data_len);
let result = format!("{book_title}{annotations}{authors}{translators}{sequences}{genres}{download_links}"); let result = format!(
"{book_title}{annotations}{authors}{translators}{sequences}{genres}{download_links}"
);
let result_len = result.len(); let result_len = result.len();
FormatResult { FormatResult {
result, result,
current_size: result_len, current_size: result_len,
max_size: max_result_size + required_data_len max_size: max_result_size + required_data_len,
} }
} }
} }

View File

@@ -11,7 +11,9 @@ use crate::config;
use self::types::Empty; use self::types::Empty;
fn get_allowed_langs_params(allowed_langs: SmallVec<[SmartString; 3]>) -> Vec<(&'static str, SmartString)> { fn get_allowed_langs_params(
allowed_langs: SmallVec<[SmartString; 3]>,
) -> Vec<(&'static str, SmartString)> {
allowed_langs allowed_langs
.into_iter() .into_iter()
.map(|lang| ("allowed_langs", lang)) .map(|lang| ("allowed_langs", lang))
@@ -38,13 +40,11 @@ where
Err(err) => { Err(err) => {
log::error!("Failed serialization: url={:?} err={:?}", url, err); log::error!("Failed serialization: url={:?} err={:?}", url, err);
Err(Box::new(err)) Err(Box::new(err))
}, }
} }
} }
pub async fn get_book( pub async fn get_book(id: u32) -> Result<types::Book, Box<dyn std::error::Error + Send + Sync>> {
id: u32,
) -> Result<types::Book , Box<dyn std::error::Error + Send + Sync>> {
_make_request(&format!("/api/v1/books/{id}"), vec![]).await _make_request(&format!("/api/v1/books/{id}"), vec![]).await
} }
@@ -169,7 +169,10 @@ pub async fn get_author_books(
id: u32, id: u32,
page: u32, page: u32,
allowed_langs: SmallVec<[SmartString; 3]>, allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<types::Page<types::AuthorBook, types::BookAuthor>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<
types::Page<types::AuthorBook, types::BookAuthor>,
Box<dyn std::error::Error + Send + Sync>,
> {
let mut params = get_allowed_langs_params(allowed_langs); let mut params = get_allowed_langs_params(allowed_langs);
params.push(("page", page.to_string().into())); params.push(("page", page.to_string().into()));
@@ -182,7 +185,10 @@ pub async fn get_translator_books(
id: u32, id: u32,
page: u32, page: u32,
allowed_langs: SmallVec<[SmartString; 3]>, allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<types::Page<types::TranslatorBook, types::BookTranslator>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<
types::Page<types::TranslatorBook, types::BookTranslator>,
Box<dyn std::error::Error + Send + Sync>,
> {
let mut params = get_allowed_langs_params(allowed_langs); let mut params = get_allowed_langs_params(allowed_langs);
params.push(("page", page.to_string().into())); params.push(("page", page.to_string().into()));
@@ -195,7 +201,10 @@ pub async fn get_sequence_books(
id: u32, id: u32,
page: u32, page: u32,
allowed_langs: SmallVec<[SmartString; 3]>, allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<types::Page<types::SequenceBook, types::Sequence>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<
types::Page<types::SequenceBook, types::Sequence>,
Box<dyn std::error::Error + Send + Sync>,
> {
let mut params = get_allowed_langs_params(allowed_langs); let mut params = get_allowed_langs_params(allowed_langs);
params.push(("page", page.to_string().into())); params.push(("page", page.to_string().into()));
@@ -226,7 +235,11 @@ pub async fn get_author_books_available_types(
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
let params = get_allowed_langs_params(allowed_langs); let params = get_allowed_langs_params(allowed_langs);
_make_request(format!("/api/v1/authors/{id}/available_types").as_str(), params).await _make_request(
format!("/api/v1/authors/{id}/available_types").as_str(),
params,
)
.await
} }
pub async fn get_translator_books_available_types( pub async fn get_translator_books_available_types(
@@ -235,14 +248,22 @@ pub async fn get_translator_books_available_types(
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
let params = get_allowed_langs_params(allowed_langs); let params = get_allowed_langs_params(allowed_langs);
_make_request(format!("/api/v1/translators/{id}/available_types").as_str(), params).await _make_request(
format!("/api/v1/translators/{id}/available_types").as_str(),
params,
)
.await
} }
pub async fn get_sequence_books_available_types( pub async fn get_sequence_books_available_types(
id: u32, id: u32,
allowed_langs: SmallVec<[SmartString; 3]> allowed_langs: SmallVec<[SmartString; 3]>,
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
let params = get_allowed_langs_params(allowed_langs); let params = get_allowed_langs_params(allowed_langs);
_make_request(format!("/api/v1/sequences/{id}/available_types").as_str(), params).await _make_request(
format!("/api/v1/sequences/{id}/available_types").as_str(),
params,
)
.await
} }

View File

@@ -4,7 +4,6 @@ use smallvec::SmallVec;
use super::formatters::{Format, FormatResult, FormatTitle}; use super::formatters::{Format, FormatResult, FormatTitle};
#[derive(Default, Deserialize, Debug, Clone)] #[derive(Default, Deserialize, Debug, Clone)]
pub struct BookAuthor { pub struct BookAuthor {
pub id: u32, pub id: u32,
@@ -87,13 +86,13 @@ pub struct Page<T, P> {
pub pages: u32, pub pages: u32,
#[serde(default)] #[serde(default)]
pub parent_item: Option<P> pub parent_item: Option<P>,
} }
impl<T, P> Page<T, P> impl<T, P> Page<T, P>
where where
T: Format + Clone + Debug, T: Format + Clone + Debug,
P: FormatTitle + Clone + Debug P: FormatTitle + Clone + Debug,
{ {
pub fn format(&self, page: u32, max_size: usize) -> String { pub fn format(&self, page: u32, max_size: usize) -> String {
let title: String = match &self.parent_item { let title: String = match &self.parent_item {
@@ -105,7 +104,7 @@ where
} }
format!("{item_title}\n\n\n") format!("{item_title}\n\n\n")
}, }
None => "".to_string(), None => "".to_string(),
}; };
@@ -124,7 +123,8 @@ where
let items_count: usize = self.items.len(); let items_count: usize = self.items.len();
let item_size: usize = (max_size - separator_len * items_count) / items_count; let item_size: usize = (max_size - separator_len * items_count) / items_count;
let format_result: Vec<FormatResult> = self.items let format_result: Vec<FormatResult> = self
.items
.iter() .iter()
.map(|item| item.format(item_size)) .map(|item| item.format(item_size))
.collect(); .collect();
@@ -232,7 +232,7 @@ impl From<SearchBook> for Book {
translators: value.translators, translators: value.translators,
sequences: value.sequences, sequences: value.sequences,
genres: vec![], genres: vec![],
pages: None pages: None,
} }
} }
} }
@@ -262,7 +262,7 @@ impl From<AuthorBook> for Book {
translators: value.translators, translators: value.translators,
sequences: value.sequences, sequences: value.sequences,
genres: vec![], genres: vec![],
pages: None pages: None,
} }
} }
} }
@@ -292,12 +292,11 @@ impl From<TranslatorBook> for Book {
translators: vec![], translators: vec![],
sequences: value.sequences, sequences: value.sequences,
genres: vec![], genres: vec![],
pages: None pages: None,
} }
} }
} }
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct SequenceBook { pub struct SequenceBook {
pub id: u32, pub id: u32,
@@ -323,7 +322,7 @@ impl From<SequenceBook> for Book {
translators: value.translators, translators: value.translators,
sequences: vec![], sequences: vec![],
genres: vec![], genres: vec![],
pages: None pages: None,
} }
} }
} }

View File

@@ -1,22 +1,36 @@
use teloxide::{types::Message, adaptors::{CacheMe, Throttle}, Bot}; use teloxide::{
adaptors::{CacheMe, Throttle},
types::Message,
Bot,
};
use crate::{bots::{BotHandlerInternal, approved_bot::modules::support::support_command_handler}, bots_manager::CHAT_DONATION_NOTIFICATIONS_CACHE}; use crate::{
bots::{approved_bot::modules::support::support_command_handler, BotHandlerInternal},
bots_manager::CHAT_DONATION_NOTIFICATIONS_CACHE,
};
use super::user_settings::{is_need_donate_notifications, mark_donate_notification_sent}; use super::user_settings::{is_need_donate_notifications, mark_donate_notification_sent};
pub async fn send_donation_notification( pub async fn send_donation_notification(
bot: CacheMe<Throttle<Bot>>, bot: CacheMe<Throttle<Bot>>,
message: Message, message: Message,
) -> BotHandlerInternal { ) -> BotHandlerInternal {
if CHAT_DONATION_NOTIFICATIONS_CACHE.get(&message.chat.id).await.is_some() { if CHAT_DONATION_NOTIFICATIONS_CACHE
.get(&message.chat.id)
.await
.is_some()
{
return Ok(()); return Ok(());
} else if !is_need_donate_notifications(message.chat.id).await? { } else if !is_need_donate_notifications(message.chat.id).await? {
CHAT_DONATION_NOTIFICATIONS_CACHE.insert(message.chat.id, ()).await; CHAT_DONATION_NOTIFICATIONS_CACHE
.insert(message.chat.id, ())
.await;
return Ok(()); return Ok(());
} }
CHAT_DONATION_NOTIFICATIONS_CACHE.insert(message.chat.id, ()).await; CHAT_DONATION_NOTIFICATIONS_CACHE
.insert(message.chat.id, ())
.await;
mark_donate_notification_sent(message.chat.id).await?; mark_donate_notification_sent(message.chat.id).await?;
support_command_handler(message, bot).await?; support_command_handler(message, bot).await?;

View File

@@ -1,5 +1,5 @@
pub mod batch_downloader;
pub mod book_cache; pub mod book_cache;
pub mod book_library; pub mod book_library;
pub mod user_settings;
pub mod donation_notifications; pub mod donation_notifications;
pub mod batch_downloader; pub mod user_settings;

View File

@@ -1,10 +1,10 @@
use serde::Deserialize; use serde::Deserialize;
use serde_json::json; use serde_json::json;
use smallvec::{SmallVec, smallvec}; use smallvec::{smallvec, SmallVec};
use teloxide::types::{UserId, ChatId};
use smartstring::alias::String as SmartString; use smartstring::alias::String as SmartString;
use teloxide::types::{ChatId, UserId};
use crate::{config, bots_manager::USER_LANGS_CACHE}; use crate::{bots_manager::USER_LANGS_CACHE, config};
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct Lang { pub struct Lang {
@@ -40,25 +40,20 @@ pub async fn get_user_settings(
Ok(response.json::<UserSettings>().await?) Ok(response.json::<UserSettings>().await?)
} }
pub async fn get_user_or_default_lang_codes( pub async fn get_user_or_default_lang_codes(user_id: UserId) -> SmallVec<[SmartString; 3]> {
user_id: UserId,
) -> SmallVec<[SmartString; 3]> {
if let Some(cached_langs) = USER_LANGS_CACHE.get(&user_id).await { if let Some(cached_langs) = USER_LANGS_CACHE.get(&user_id).await {
return cached_langs; return cached_langs;
} }
let default_lang_codes = smallvec![ let default_lang_codes = smallvec!["ru".into(), "be".into(), "uk".into()];
"ru".into(),
"be".into(),
"uk".into()
];
match get_user_settings(user_id).await { match get_user_settings(user_id).await {
Ok(v) => { Ok(v) => {
let langs: SmallVec<[SmartString; 3]> = v.allowed_langs.into_iter().map(|lang| lang.code).collect(); let langs: SmallVec<[SmartString; 3]> =
v.allowed_langs.into_iter().map(|lang| lang.code).collect();
USER_LANGS_CACHE.insert(user_id, langs.clone()).await; USER_LANGS_CACHE.insert(user_id, langs.clone()).await;
langs langs
}, }
Err(_) => default_lang_codes, Err(_) => default_lang_codes,
} }
} }
@@ -109,7 +104,10 @@ pub async fn update_user_activity(
user_id: UserId, user_id: UserId,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
reqwest::Client::new() reqwest::Client::new()
.post(format!("{}/users/{user_id}/update_activity", &config::CONFIG.user_settings_url)) .post(format!(
"{}/users/{user_id}/update_activity",
&config::CONFIG.user_settings_url
))
.header("Authorization", &config::CONFIG.user_settings_api_key) .header("Authorization", &config::CONFIG.user_settings_api_key)
.send() .send()
.await? .await?
@@ -118,9 +116,14 @@ pub async fn update_user_activity(
Ok(()) Ok(())
} }
pub async fn is_need_donate_notifications(chat_id: ChatId) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> { pub async fn is_need_donate_notifications(
chat_id: ChatId,
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
let response = reqwest::Client::new() let response = reqwest::Client::new()
.get(format!("{}/donate_notifications/{chat_id}/is_need_send", &config::CONFIG.user_settings_url)) .get(format!(
"{}/donate_notifications/{chat_id}/is_need_send",
&config::CONFIG.user_settings_url
))
.header("Authorization", &config::CONFIG.user_settings_api_key) .header("Authorization", &config::CONFIG.user_settings_api_key)
.send() .send()
.await? .await?
@@ -129,9 +132,14 @@ pub async fn is_need_donate_notifications(chat_id: ChatId) -> Result<bool, Box<d
Ok(response.json::<bool>().await?) Ok(response.json::<bool>().await?)
} }
pub async fn mark_donate_notification_sent(chat_id: ChatId) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { pub async fn mark_donate_notification_sent(
chat_id: ChatId,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
reqwest::Client::new() reqwest::Client::new()
.post(format!("{}/donate_notifications/{chat_id}", &config::CONFIG.user_settings_url)) .post(format!(
"{}/donate_notifications/{chat_id}",
&config::CONFIG.user_settings_url
))
.header("Authorization", &config::CONFIG.user_settings_api_key) .header("Authorization", &config::CONFIG.user_settings_api_key)
.send() .send()
.await? .await?

View File

@@ -1,6 +1,5 @@
use teloxide::{dptree, types::CallbackQuery}; use teloxide::{dptree, types::CallbackQuery};
pub fn filter_callback_query<T>() -> crate::bots::BotHandler pub fn filter_callback_query<T>() -> crate::bots::BotHandler
where where
T: std::str::FromStr + Send + Sync + 'static, T: std::str::FromStr + Send + Sync + 'static,

View File

@@ -1,4 +1,7 @@
use teloxide::{prelude::*, adaptors::{Throttle, CacheMe}}; use teloxide::{
adaptors::{CacheMe, Throttle},
prelude::*,
};
use std::error::Error; use std::error::Error;
@@ -23,7 +26,6 @@ pub async fn message_handler(
register::RegisterStatus::RegisterFail => strings::ALREADY_REGISTERED.to_string(), register::RegisterStatus::RegisterFail => strings::ALREADY_REGISTERED.to_string(),
}; };
bot.send_message(message.chat.id, message_text) bot.send_message(message.chat.id, message_text)
.reply_to_message_id(message.id) .reply_to_message_id(message.id)
.await?; .await?;
@@ -39,6 +41,9 @@ pub fn get_manager_handler() -> Handler<
> { > {
Update::filter_message().branch( Update::filter_message().branch(
Message::filter_text() Message::filter_text()
.chain(dptree::filter(|message: Message| { get_token(message.text().unwrap()).is_some() })).endpoint(message_handler), .chain(dptree::filter(|message: Message| {
get_token(message.text().unwrap()).is_some()
}))
.endpoint(message_handler),
) )
} }

View File

@@ -6,15 +6,13 @@ use tracing::log;
use crate::config; use crate::config;
#[derive(Debug)] #[derive(Debug)]
pub enum RegisterStatus { pub enum RegisterStatus {
Success {username: String}, Success { username: String },
WrongToken, WrongToken,
RegisterFail, RegisterFail,
} }
async fn get_bot_username(token: &str) -> Option<String> { async fn get_bot_username(token: &str) -> Option<String> {
match Bot::new(token).get_me().send().await { match Bot::new(token).get_me().send().await {
Ok(v) => v.username.clone(), Ok(v) => v.username.clone(),
@@ -25,7 +23,11 @@ async fn get_bot_username(token: &str) -> Option<String> {
} }
} }
async fn make_register_request(user_id: UserId, username: &str, token: &str) -> Result<(), Box<dyn Error + Send + Sync>> { async fn make_register_request(
user_id: UserId,
username: &str,
token: &str,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let body = json!({ let body = json!({
"token": token, "token": token,
"user": user_id, "user": user_id,
@@ -46,13 +48,12 @@ async fn make_register_request(user_id: UserId, username: &str, token: &str) ->
Ok(()) Ok(())
} }
pub async fn register(user_id: UserId, message_text: &str) -> RegisterStatus { pub async fn register(user_id: UserId, message_text: &str) -> RegisterStatus {
let token = super::utils::get_token(message_text).unwrap(); let token = super::utils::get_token(message_text).unwrap();
let bot_username = match get_bot_username(token).await { let bot_username = match get_bot_username(token).await {
Some(v) => v, Some(v) => v,
None => return RegisterStatus::WrongToken None => return RegisterStatus::WrongToken,
}; };
let register_request_status = make_register_request(user_id, &bot_username, token).await; let register_request_status = make_register_request(user_id, &bot_username, token).await;
@@ -63,5 +64,7 @@ pub async fn register(user_id: UserId, message_text: &str) -> RegisterStatus {
return RegisterStatus::RegisterFail; return RegisterStatus::RegisterFail;
} }
RegisterStatus::Success { username: bot_username } RegisterStatus::Success {
username: bot_username,
}
} }

View File

@@ -1,7 +1,10 @@
pub fn format_registered_message(username: &str) -> String { pub fn format_registered_message(username: &str) -> String {
format!("@{username} зарегистрирован и через несколько минут будет подключен!", username = username) format!(
"@{username} зарегистрирован и через несколько минут будет подключен!",
username = username
)
} }
pub const ALREADY_REGISTERED: &str= "Ошибка! Возможно бот уже зарегистрирован!"; pub const ALREADY_REGISTERED: &str = "Ошибка! Возможно бот уже зарегистрирован!";
pub const ERROR_MESSAGE: &str = "Ошибка! Что-то не так с ботом!"; pub const ERROR_MESSAGE: &str = "Ошибка! Что-то не так с ботом!";

View File

@@ -1,16 +1,14 @@
use regex::Regex; use regex::Regex;
pub fn get_token(message_text: &str) -> Option<&str> { pub fn get_token(message_text: &str) -> Option<&str> {
let re = Regex::new("(?P<token>[0-9]+:[0-9a-zA-Z-_]+)").unwrap(); let re = Regex::new("(?P<token>[0-9]+:[0-9a-zA-Z-_]+)").unwrap();
match re.find(message_text) { match re.find(message_text) {
Some(v) => Some(v.as_str()), Some(v) => Some(v.as_str()),
None => None None => None,
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -42,7 +40,10 @@ mod tests {
let result = get_token(message); let result = get_token(message);
assert_eq!(result.unwrap(), "5555555555:AAF-AAAAAAAA1239AA2AAsvy13Axp23RAa"); assert_eq!(
result.unwrap(),
"5555555555:AAF-AAAAAAAA1239AA2AAsvy13Axp23RAa"
);
} }
#[test] #[test]

View File

@@ -18,25 +18,14 @@ type BotCommands = Option<Vec<teloxide::types::BotCommand>>;
fn ignore_channel_messages() -> crate::bots::BotHandler { fn ignore_channel_messages() -> crate::bots::BotHandler {
dptree::entry() dptree::entry()
.branch( .branch(Update::filter_channel_post().endpoint(|| async { Ok(()) }))
Update::filter_channel_post() .branch(Update::filter_edited_channel_post().endpoint(|| async { Ok(()) }))
.endpoint(|| async { Ok(()) })
).branch(
Update::filter_edited_channel_post()
.endpoint(|| async { Ok(()) })
)
} }
fn ignore_chat_member_update() -> crate::bots::BotHandler { fn ignore_chat_member_update() -> crate::bots::BotHandler {
dptree::entry() dptree::entry()
.branch( .branch(Update::filter_chat_member().endpoint(|| async { Ok(()) }))
Update::filter_chat_member() .branch(Update::filter_my_chat_member().endpoint(|| async { Ok(()) }))
.endpoint(|| async { Ok(()) })
)
.branch(
Update::filter_my_chat_member()
.endpoint(|| async { Ok(()) })
)
} }
pub fn get_bot_handler() -> (BotHandler, BotCommands) { pub fn get_bot_handler() -> (BotHandler, BotCommands) {

View File

@@ -2,7 +2,6 @@ use serde::Deserialize;
use crate::config; use crate::config;
#[derive(Deserialize, Debug, PartialEq, Clone, Copy)] #[derive(Deserialize, Debug, PartialEq, Clone, Copy)]
pub enum BotCache { pub enum BotCache {
#[serde(rename = "original")] #[serde(rename = "original")]

View File

@@ -1,14 +1,14 @@
pub mod bot_manager_client; pub mod bot_manager_client;
use axum::extract::{State, Path}; use axum::extract::{Path, State};
use axum::response::IntoResponse; use axum::response::IntoResponse;
use axum::routing::post; use axum::routing::post;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use reqwest::StatusCode; use reqwest::StatusCode;
use smartstring::alias::String as SmartString; use smartstring::alias::String as SmartString;
use teloxide::stop::{mk_stop_token, StopToken, StopFlag}; use teloxide::stop::{mk_stop_token, StopFlag, StopToken};
use teloxide::update_listeners::{StatefulListener, UpdateListener}; use teloxide::update_listeners::{StatefulListener, UpdateListener};
use tokio::sync::mpsc::{UnboundedSender, self}; use tokio::sync::mpsc::{self, UnboundedSender};
use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_stream::wrappers::UnboundedReceiverStream;
use tracing::log; use tracing::log;
use url::Url; use url::Url;
@@ -24,7 +24,7 @@ use tokio::sync::RwLock;
use smallvec::SmallVec; use smallvec::SmallVec;
use teloxide::adaptors::throttle::Limits; use teloxide::adaptors::throttle::Limits;
use teloxide::types::{BotCommand, UpdateKind}; use teloxide::types::{BotCommand, UpdateKind};
use tokio::time::{sleep, Duration, self}; use tokio::time::{self, sleep, Duration};
use tower_http::trace::TraceLayer; use tower_http::trace::TraceLayer;
use teloxide::prelude::*; use teloxide::prelude::*;
@@ -35,15 +35,12 @@ use self::bot_manager_client::get_bots;
pub use self::bot_manager_client::{BotCache, BotData}; pub use self::bot_manager_client::{BotCache, BotData};
use crate::config; use crate::config;
type UpdateSender = mpsc::UnboundedSender<Result<Update, std::convert::Infallible>>; type UpdateSender = mpsc::UnboundedSender<Result<Update, std::convert::Infallible>>;
fn tuple_first_mut<A, B>(tuple: &mut (A, B)) -> &mut A { fn tuple_first_mut<A, B>(tuple: &mut (A, B)) -> &mut A {
&mut tuple.0 &mut tuple.0
} }
pub static USER_ACTIVITY_CACHE: Lazy<Cache<UserId, ()>> = Lazy::new(|| { pub static USER_ACTIVITY_CACHE: Lazy<Cache<UserId, ()>> = Lazy::new(|| {
Cache::builder() Cache::builder()
.time_to_idle(Duration::from_secs(5 * 60)) .time_to_idle(Duration::from_secs(5 * 60))
@@ -65,9 +62,17 @@ pub static CHAT_DONATION_NOTIFICATIONS_CACHE: Lazy<Cache<ChatId, ()>> = Lazy::ne
.build() .build()
}); });
type Routes = Arc<
type Routes = Arc<RwLock<HashMap<String, (StopToken, ClosableSender<Result<Update, std::convert::Infallible>>)>>>; RwLock<
HashMap<
String,
(
StopToken,
ClosableSender<Result<Update, std::convert::Infallible>>,
),
>,
>,
>;
struct ClosableSender<T> { struct ClosableSender<T> {
origin: std::sync::Arc<std::sync::RwLock<Option<mpsc::UnboundedSender<T>>>>, origin: std::sync::Arc<std::sync::RwLock<Option<mpsc::UnboundedSender<T>>>>,
@@ -75,13 +80,17 @@ struct ClosableSender<T> {
impl<T> Clone for ClosableSender<T> { impl<T> Clone for ClosableSender<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { origin: self.origin.clone() } Self {
origin: self.origin.clone(),
}
} }
} }
impl<T> ClosableSender<T> { impl<T> ClosableSender<T> {
fn new(sender: mpsc::UnboundedSender<T>) -> Self { fn new(sender: mpsc::UnboundedSender<T>) -> Self {
Self { origin: std::sync::Arc::new(std::sync::RwLock::new(Some(sender))) } Self {
origin: std::sync::Arc::new(std::sync::RwLock::new(Some(sender))),
}
} }
fn get(&self) -> Option<mpsc::UnboundedSender<T>> { fn get(&self) -> Option<mpsc::UnboundedSender<T>> {
@@ -93,7 +102,6 @@ impl<T> ClosableSender<T> {
} }
} }
#[derive(Default, Clone)] #[derive(Default, Clone)]
struct ServerState { struct ServerState {
routers: Routes, routers: Routes,
@@ -102,7 +110,7 @@ struct ServerState {
pub struct BotsManager { pub struct BotsManager {
port: u16, port: u16,
state: ServerState state: ServerState,
} }
impl BotsManager { impl BotsManager {
@@ -111,12 +119,19 @@ impl BotsManager {
port: 8000, port: 8000,
state: ServerState { state: ServerState {
routers: Arc::new(RwLock::new(HashMap::new())) routers: Arc::new(RwLock::new(HashMap::new())),
} },
} }
} }
fn get_listener(&self) -> (StopToken, StopFlag, UnboundedSender<Result<Update, std::convert::Infallible>>, impl UpdateListener<Err = Infallible>) { fn get_listener(
&self,
) -> (
StopToken,
StopFlag,
UnboundedSender<Result<Update, std::convert::Infallible>>,
impl UpdateListener<Err = Infallible>,
) {
let (tx, rx): (UpdateSender, _) = mpsc::unbounded_channel(); let (tx, rx): (UpdateSender, _) = mpsc::unbounded_channel();
let (stop_token, stop_flag) = mk_stop_token(); let (stop_token, stop_flag) = mk_stop_token();
@@ -126,9 +141,7 @@ impl BotsManager {
let listener = StatefulListener::new( let listener = StatefulListener::new(
(stream, stop_token.clone()), (stream, stop_token.clone()),
tuple_first_mut, tuple_first_mut,
|state: &mut (_, StopToken)| { |state: &mut (_, StopToken)| state.1.clone(),
state.1.clone()
},
); );
(stop_token, stop_flag, tx, listener) (stop_token, stop_flag, tx, listener)
@@ -187,7 +200,7 @@ impl BotsManager {
true true
} }
async fn check(&mut self){ async fn check(&mut self) {
let bots_data = get_bots().await; let bots_data = get_bots().await;
match bots_data { match bots_data {
@@ -202,7 +215,7 @@ impl BotsManager {
self.start_bot(bot_data).await; self.start_bot(bot_data).await;
} }
} }
}, }
Err(err) => { Err(err) => {
log::info!("{:?}", err); log::info!("{:?}", err);
} }
@@ -215,7 +228,6 @@ impl BotsManager {
Path(token): Path<String>, Path(token): Path<String>,
input: String, input: String,
) -> impl IntoResponse { ) -> impl IntoResponse {
let routes = routers.read().await; let routes = routers.read().await;
let tx = routes.get(&token); let tx = routes.get(&token);
@@ -232,7 +244,7 @@ impl BotsManager {
sender.close(); sender.close();
}; };
return StatusCode::SERVICE_UNAVAILABLE; return StatusCode::SERVICE_UNAVAILABLE;
}, }
}; };
match serde_json::from_str::<Update>(&input) { match serde_json::from_str::<Update>(&input) {

View File

@@ -61,6 +61,4 @@ impl Config {
} }
} }
pub static CONFIG: Lazy<Config> = Lazy::new(|| { pub static CONFIG: Lazy<Config> = Lazy::new(Config::load);
Config::load()
});

View File

@@ -2,15 +2,14 @@ use std::str::FromStr;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use sentry::ClientOptions;
use sentry::integrations::debug_images::DebugImagesIntegration; use sentry::integrations::debug_images::DebugImagesIntegration;
use sentry::types::Dsn; use sentry::types::Dsn;
use sentry::ClientOptions;
mod bots; mod bots;
mod bots_manager; mod bots_manager;
mod config; mod config;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
tracing_subscriber::fmt() tracing_subscriber::fmt()