mirror of
https://github.com/flibusta-apps/book_bot.git
synced 2025-12-06 07:25:36 +01:00
Add pre-commit
This commit is contained in:
7
.pre-commit-config.yaml
Normal file
7
.pre-commit-config.yaml
Normal 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
|
||||
@@ -2,9 +2,16 @@ pub mod modules;
|
||||
pub mod services;
|
||||
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::{
|
||||
modules::{
|
||||
@@ -16,12 +23,12 @@ use self::{
|
||||
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(
|
||||
me: teloxide::types::Me,
|
||||
user: teloxide::types::User,
|
||||
) -> Option<()> {
|
||||
async fn _update_activity(me: teloxide::types::Me, user: teloxide::types::User) -> Option<()> {
|
||||
if USER_ACTIVITY_CACHE.contains_key(&user.id) {
|
||||
return None;
|
||||
}
|
||||
@@ -39,7 +46,9 @@ async fn _update_activity(
|
||||
user.username.clone().unwrap_or("".to_string()),
|
||||
me.username.clone().unwrap(),
|
||||
allowed_langs,
|
||||
).await.is_ok()
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
update_result = update_user_activity(user.id).await;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ use std::str::FromStr;
|
||||
|
||||
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)]
|
||||
pub enum AnnotationCallbackData {
|
||||
@@ -19,17 +20,20 @@ impl FromStr for AnnotationCallbackData {
|
||||
.unwrap_or_else(|_| panic!("Broken AnnotationCallbackData regex pattern!"))
|
||||
.captures(s)
|
||||
.ok_or(CallbackQueryParseError)
|
||||
.map(|caps| (
|
||||
.map(|caps| {
|
||||
(
|
||||
caps["an_type"].to_string(),
|
||||
caps["id"].parse::<u32>().unwrap(),
|
||||
caps["page"].parse::<u32>().unwrap()
|
||||
))
|
||||
.map(|(annotation_type, id, page)|
|
||||
match annotation_type.as_str() {
|
||||
caps["page"].parse::<u32>().unwrap(),
|
||||
)
|
||||
})
|
||||
.map(
|
||||
|(annotation_type, id, page)| match annotation_type.as_str() {
|
||||
"a" => AnnotationCallbackData::Author { id, page },
|
||||
"b" => AnnotationCallbackData::Book { id, page },
|
||||
_ => panic!("Unknown AnnotationCallbackData type: {}!", annotation_type),
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
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)]
|
||||
pub enum AnnotationCommand {
|
||||
@@ -15,16 +16,18 @@ impl CommandParse<Self> for AnnotationCommand {
|
||||
.unwrap_or_else(|_| panic!("Broken AnnotationCommand regexp!"))
|
||||
.captures(&s.replace(&format!("@{bot_name}"), ""))
|
||||
.ok_or(CommandParseError)
|
||||
.map(|caps| (
|
||||
.map(|caps| {
|
||||
(
|
||||
caps["an_type"].to_string(),
|
||||
caps["id"].parse::<u32>().unwrap_or_else(|_| panic!("Can't get id from AnnotationCommand!"))
|
||||
))
|
||||
.map(|(annotation_type, id)| {
|
||||
match annotation_type.as_str() {
|
||||
caps["id"]
|
||||
.parse::<u32>()
|
||||
.unwrap_or_else(|_| panic!("Can't get id from AnnotationCommand!")),
|
||||
)
|
||||
})
|
||||
.map(|(annotation_type, id)| match annotation_type.as_str() {
|
||||
"a" => Ok(AnnotationCommand::Author { id }),
|
||||
"b" => Ok(AnnotationCommand::Book { id }),
|
||||
_ => panic!("Unknown AnnotationCommand type: {}!", annotation_type),
|
||||
}
|
||||
})?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@ use std::fmt;
|
||||
|
||||
use super::commands::AnnotationCommand;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AnnotationFormatError {
|
||||
pub command: AnnotationCommand,
|
||||
pub text: String
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for AnnotationFormatError {
|
||||
|
||||
@@ -21,7 +21,6 @@ impl AnnotationFormat for BookAnnotation {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl AnnotationFormat for AuthorAnnotation {
|
||||
fn get_file(&self) -> Option<&String> {
|
||||
self.file.as_ref()
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
pub mod commands;
|
||||
pub mod callback_data;
|
||||
pub mod formatter;
|
||||
pub mod commands;
|
||||
pub mod errors;
|
||||
pub mod formatter;
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
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 crate::bots::{
|
||||
approved_bot::{
|
||||
modules::utils::pagination::generic_get_pagination_keyboard,
|
||||
services::book_library::{
|
||||
get_author_annotation, get_book_annotation,
|
||||
},
|
||||
services::book_library::{get_author_annotation, get_book_annotation},
|
||||
tools::filter_callback_query,
|
||||
},
|
||||
BotHandlerInternal,
|
||||
};
|
||||
|
||||
use self::{commands::AnnotationCommand, formatter::AnnotationFormat, callback_data::AnnotationCallbackData, errors::AnnotationFormatError};
|
||||
|
||||
use super::utils::{split_text::split_text_to_chunks, filter_command::filter_command};
|
||||
use self::{
|
||||
callback_data::AnnotationCallbackData, commands::AnnotationCommand,
|
||||
errors::AnnotationFormatError, formatter::AnnotationFormat,
|
||||
};
|
||||
|
||||
use super::utils::{filter_command::filter_command, split_text::split_text_to_chunks};
|
||||
|
||||
async fn download_image(
|
||||
file: &String,
|
||||
@@ -32,7 +38,6 @@ async fn download_image(
|
||||
Ok(reqwest::get(file).await?.error_for_status()?)
|
||||
}
|
||||
|
||||
|
||||
pub async fn send_annotation_handler<T, Fut>(
|
||||
message: Message,
|
||||
bot: CacheMe<Throttle<Bot>>,
|
||||
@@ -72,9 +77,9 @@ where
|
||||
.into_async_read()
|
||||
.compat();
|
||||
|
||||
#[allow(unused_must_use)] {
|
||||
bot
|
||||
.send_photo(message.chat.id, InputFile::read(data))
|
||||
#[allow(unused_must_use)]
|
||||
{
|
||||
bot.send_photo(message.chat.id, InputFile::read(data))
|
||||
.send()
|
||||
.await;
|
||||
}
|
||||
@@ -82,7 +87,10 @@ where
|
||||
};
|
||||
|
||||
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();
|
||||
@@ -93,15 +101,10 @@ where
|
||||
AnnotationCommand::Book { id } => AnnotationCallbackData::Book { id, page: 1 },
|
||||
AnnotationCommand::Author { id } => AnnotationCallbackData::Author { id, page: 1 },
|
||||
};
|
||||
let keyboard = generic_get_pagination_keyboard(
|
||||
1,
|
||||
chunked_text.len().try_into()?,
|
||||
callback_data,
|
||||
false,
|
||||
);
|
||||
let keyboard =
|
||||
generic_get_pagination_keyboard(1, chunked_text.len().try_into()?, callback_data, false);
|
||||
|
||||
bot
|
||||
.send_message(message.chat.id, current_text)
|
||||
bot.send_message(message.chat.id, current_text)
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
.await?;
|
||||
@@ -109,7 +112,6 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub async fn annotation_pagination_handler<T, Fut>(
|
||||
cq: CallbackQuery,
|
||||
bot: CacheMe<Throttle<Bot>>,
|
||||
@@ -144,15 +146,10 @@ where
|
||||
};
|
||||
let current_text = chunked_text.get(page_index - 1).unwrap();
|
||||
|
||||
let keyboard = generic_get_pagination_keyboard(
|
||||
page,
|
||||
chunked_text.len().try_into()?,
|
||||
callback_data,
|
||||
false,
|
||||
);
|
||||
let keyboard =
|
||||
generic_get_pagination_keyboard(page, chunked_text.len().try_into()?, callback_data, false);
|
||||
|
||||
bot
|
||||
.edit_message_text(message.chat.id, message.id, current_text)
|
||||
bot.edit_message_text(message.chat.id, message.id, current_text)
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
.await?;
|
||||
@@ -160,7 +157,6 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub fn get_annotations_handler() -> crate::bots::BotHandler {
|
||||
dptree::entry()
|
||||
.branch(
|
||||
|
||||
@@ -2,8 +2,9 @@ use std::str::FromStr;
|
||||
|
||||
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)]
|
||||
pub enum BookCallbackData {
|
||||
@@ -20,18 +21,20 @@ impl FromStr for BookCallbackData {
|
||||
.unwrap_or_else(|_| panic!("Broken BookCallbackData regex pattern!"))
|
||||
.captures(s)
|
||||
.ok_or(CallbackQueryParseError)
|
||||
.map(|caps| (
|
||||
.map(|caps| {
|
||||
(
|
||||
caps["an_type"].to_string(),
|
||||
caps["id"].parse::<u32>().unwrap(),
|
||||
caps["page"].parse::<u32>().unwrap()
|
||||
))
|
||||
.map(|(annotation_type, id, page)|
|
||||
match annotation_type.as_str() {
|
||||
caps["page"].parse::<u32>().unwrap(),
|
||||
)
|
||||
})
|
||||
.map(
|
||||
|(annotation_type, id, page)| match annotation_type.as_str() {
|
||||
"a" => Ok(BookCallbackData::Author { id, page }),
|
||||
"t" => Ok(BookCallbackData::Translator { id, page }),
|
||||
"s" => Ok(BookCallbackData::Sequence { id, page }),
|
||||
_ => panic!("Unknown BookCallbackData type: {}!", annotation_type),
|
||||
}
|
||||
},
|
||||
)?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
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)]
|
||||
pub enum BookCommand {
|
||||
@@ -16,17 +17,12 @@ impl CommandParse<Self> for BookCommand {
|
||||
.unwrap_or_else(|_| panic!("Broken BookCommand regexp!"))
|
||||
.captures(&s.replace(&format!("@{bot_name}"), ""))
|
||||
.ok_or(CommandParseError)
|
||||
.map(|caps| (
|
||||
caps["an_type"].to_string(),
|
||||
caps["id"].parse().unwrap()
|
||||
))
|
||||
.map(|(annotation_type, id)| {
|
||||
match annotation_type.as_str() {
|
||||
.map(|caps| (caps["an_type"].to_string(), caps["id"].parse().unwrap()))
|
||||
.map(|(annotation_type, id)| match annotation_type.as_str() {
|
||||
"a" => Ok(BookCommand::Author { id }),
|
||||
"t" => Ok(BookCommand::Translator { id }),
|
||||
"s" => Ok(BookCommand::Sequence { id }),
|
||||
_ => panic!("Unknown BookCommand type: {}!", annotation_type),
|
||||
}
|
||||
})?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
pub mod commands;
|
||||
pub mod callback_data;
|
||||
pub mod commands;
|
||||
|
||||
use core::fmt::Debug;
|
||||
|
||||
use smartstring::alias::String as SmartString;
|
||||
|
||||
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 crate::bots::approved_bot::{
|
||||
services::{
|
||||
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,
|
||||
},
|
||||
user_settings::get_user_or_default_lang_codes,
|
||||
@@ -20,11 +26,10 @@ use crate::bots::approved_bot::{
|
||||
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};
|
||||
|
||||
|
||||
async fn send_book_handler<T, P, Fut>(
|
||||
message: Message,
|
||||
bot: CacheMe<Throttle<Bot>>,
|
||||
@@ -64,8 +69,7 @@ where
|
||||
let items_page = match books_getter(id, 1, allowed_langs.clone()).await {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
bot
|
||||
.send_message(chat_id, "Ошибка! Попробуйте позже :(")
|
||||
bot.send_message(chat_id, "Ошибка! Попробуйте позже :(")
|
||||
.send()
|
||||
.await?;
|
||||
return Err(err);
|
||||
@@ -73,7 +77,9 @@ where
|
||||
};
|
||||
|
||||
if items_page.pages == 0 {
|
||||
bot.send_message(chat_id, "Книги не найдены!").send().await?;
|
||||
bot.send_message(chat_id, "Книги не найдены!")
|
||||
.send()
|
||||
.await?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
@@ -87,8 +93,7 @@ where
|
||||
|
||||
let keyboard = generic_get_pagination_keyboard(1, items_page.pages, callback_data, true);
|
||||
|
||||
bot
|
||||
.send_message(chat_id, formatted_page)
|
||||
bot.send_message(chat_id, formatted_page)
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
.await?;
|
||||
@@ -120,9 +125,11 @@ where
|
||||
let (chat_id, message_id) = match (chat_id, message_id) {
|
||||
(Some(chat_id), Some(message_id)) => (chat_id, message_id),
|
||||
(Some(chat_id), None) => {
|
||||
bot.send_message(chat_id, "Повторите поиск сначала").send().await?;
|
||||
bot.send_message(chat_id, "Повторите поиск сначала")
|
||||
.send()
|
||||
.await?;
|
||||
return Ok(());
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -146,7 +153,9 @@ where
|
||||
};
|
||||
|
||||
if items_page.pages == 0 {
|
||||
bot.send_message(chat_id, "Книги не найдены!").send().await?;
|
||||
bot.send_message(chat_id, "Книги не найдены!")
|
||||
.send()
|
||||
.await?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
@@ -154,8 +163,7 @@ where
|
||||
items_page = match books_getter(id, items_page.pages, allowed_langs.clone()).await {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
bot
|
||||
.send_message(chat_id, "Ошибка! Попробуйте позже :(")
|
||||
bot.send_message(chat_id, "Ошибка! Попробуйте позже :(")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
@@ -168,8 +176,7 @@ where
|
||||
|
||||
let keyboard = generic_get_pagination_keyboard(page, items_page.pages, callback_data, true);
|
||||
|
||||
bot
|
||||
.edit_message_text(chat_id, message_id, formatted_page)
|
||||
bot.edit_message_text(chat_id, message_id, formatted_page)
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
@@ -5,7 +5,6 @@ use strum_macros::EnumIter;
|
||||
|
||||
use crate::bots::approved_bot::modules::utils::errors::CommandParseError;
|
||||
|
||||
|
||||
#[derive(Clone, EnumIter)]
|
||||
pub enum DownloadQueryData {
|
||||
DownloadData { book_id: u32, file_type: String },
|
||||
@@ -29,13 +28,13 @@ impl FromStr for DownloadQueryData {
|
||||
.unwrap_or_else(|_| panic!("Broken DownloadQueryData regexp!"))
|
||||
.captures(s)
|
||||
.ok_or(CommandParseError)
|
||||
.map(|caps| (
|
||||
.map(|caps| {
|
||||
(
|
||||
caps["book_id"].parse().unwrap(),
|
||||
caps["file_type"].to_string()
|
||||
))
|
||||
.map(|(book_id, file_type)| {
|
||||
DownloadQueryData::DownloadData { book_id, file_type }
|
||||
caps["file_type"].to_string(),
|
||||
)
|
||||
})
|
||||
.map(|(book_id, file_type)| DownloadQueryData::DownloadData { book_id, file_type })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,15 +42,19 @@ impl FromStr for DownloadQueryData {
|
||||
pub enum DownloadArchiveQueryData {
|
||||
Sequence { 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 {
|
||||
fn to_string(&self) -> String {
|
||||
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::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 file_type: String = caps["file_type"].to_string();
|
||||
|
||||
Ok(
|
||||
match caps["obj_type"].to_string().as_str() {
|
||||
Ok(match caps["obj_type"].to_string().as_str() {
|
||||
"s" => DownloadArchiveQueryData::Sequence { id, file_type },
|
||||
"a" => DownloadArchiveQueryData::Author { id, file_type },
|
||||
"t" => DownloadArchiveQueryData::Translator { id, file_type },
|
||||
_ => return Err(strum::ParseError::VariantNotFound)
|
||||
}
|
||||
)
|
||||
_ => return Err(strum::ParseError::VariantNotFound),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CheckArchiveStatus {
|
||||
pub task_id: String
|
||||
pub task_id: String,
|
||||
}
|
||||
|
||||
impl ToString for CheckArchiveStatus {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use regex::Regex;
|
||||
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)]
|
||||
pub struct StartDownloadCommand {
|
||||
@@ -39,7 +40,7 @@ impl CommandParse<Self> for StartDownloadCommand {
|
||||
pub enum DownloadArchiveCommand {
|
||||
Sequence { id: u32 },
|
||||
Author { id: u32 },
|
||||
Translator { id: u32 }
|
||||
Translator { id: u32 },
|
||||
}
|
||||
|
||||
impl ToString for DownloadArchiveCommand {
|
||||
@@ -71,7 +72,7 @@ impl CommandParse<Self> for DownloadArchiveCommand {
|
||||
"s" => Ok(DownloadArchiveCommand::Sequence { id: obj_id }),
|
||||
"a" => Ok(DownloadArchiveCommand::Author { id: obj_id }),
|
||||
"t" => Ok(DownloadArchiveCommand::Translator { id: obj_id }),
|
||||
_ => Err(CommandParseError)
|
||||
_ => Err(CommandParseError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
pub mod commands;
|
||||
pub mod callback_data;
|
||||
pub mod commands;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::Utc;
|
||||
use futures::TryStreamExt;
|
||||
|
||||
|
||||
use teloxide::{
|
||||
adaptors::{CacheMe, Throttle},
|
||||
dispatching::UpdateFilterExt,
|
||||
@@ -21,42 +20,46 @@ use tracing::log;
|
||||
use crate::{
|
||||
bots::{
|
||||
approved_bot::{
|
||||
modules::download::callback_data::DownloadArchiveQueryData,
|
||||
services::{
|
||||
batch_downloader::{create_task, get_task, Task, TaskStatus},
|
||||
batch_downloader::{CreateTaskData, TaskObjectType},
|
||||
book_cache::{
|
||||
download_file, get_cached_message,
|
||||
types::{CachedMessage, DownloadFile}, download_file_by_link, get_download_link,
|
||||
download_file, download_file_by_link, get_cached_message, 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},
|
||||
donation_notifications::send_donation_notification, user_settings::get_user_or_default_lang_codes, batch_downloader::{TaskObjectType, CreateTaskData},
|
||||
batch_downloader::{create_task, get_task, TaskStatus, Task}
|
||||
|
||||
book_library::{
|
||||
get_author_books_available_types, get_book, get_sequence_books_available_types,
|
||||
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,
|
||||
},
|
||||
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;
|
||||
|
||||
|
||||
fn get_check_keyboard(task_id: String) -> InlineKeyboardMarkup {
|
||||
InlineKeyboardMarkup {
|
||||
inline_keyboard: vec![
|
||||
vec![InlineKeyboardButton {
|
||||
inline_keyboard: vec![vec![InlineKeyboardButton {
|
||||
kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(
|
||||
(CheckArchiveStatus { task_id }).to_string(),
|
||||
),
|
||||
text: String::from("Обновить статус"),
|
||||
}],
|
||||
],
|
||||
}]],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async fn _send_cached(
|
||||
message: &Message,
|
||||
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)
|
||||
.await?;
|
||||
send_with_download_from_channel(message, bot, download_data, need_delete_message).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -137,7 +139,10 @@ async fn send_with_download_from_channel(
|
||||
) -> BotHandlerInternal {
|
||||
match download_file(&download_data).await {
|
||||
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?;
|
||||
return Ok(());
|
||||
};
|
||||
@@ -147,7 +152,7 @@ async fn send_with_download_from_channel(
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
@@ -162,18 +167,17 @@ async fn send_download_link(
|
||||
Err(err) => {
|
||||
log::error!("{:?}", err);
|
||||
return Err(err);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
bot
|
||||
.edit_message_text(
|
||||
bot.edit_message_text(
|
||||
message.chat.id,
|
||||
message.id,
|
||||
format!(
|
||||
"Файл не может быть загружен в чат! \n \
|
||||
Вы можете скачать его <a href=\"{}\">по ссылке</a> (работает 3 часа)",
|
||||
link_data.link
|
||||
)
|
||||
),
|
||||
)
|
||||
.parse_mode(ParseMode::Html)
|
||||
.reply_markup(InlineKeyboardMarkup {
|
||||
@@ -193,22 +197,10 @@ async fn download_handler(
|
||||
) -> BotHandlerInternal {
|
||||
match cache {
|
||||
BotCache::Original => {
|
||||
send_cached_message(
|
||||
message,
|
||||
bot,
|
||||
download_data,
|
||||
need_delete_message,
|
||||
)
|
||||
.await
|
||||
send_cached_message(message, bot, download_data, need_delete_message).await
|
||||
}
|
||||
BotCache::NoCache => {
|
||||
send_with_download_from_channel(
|
||||
message,
|
||||
bot,
|
||||
download_data,
|
||||
need_delete_message,
|
||||
)
|
||||
.await
|
||||
send_with_download_from_channel(message, bot, download_data, need_delete_message).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,9 +227,7 @@ async fn get_download_keyboard_handler(
|
||||
.into_iter()
|
||||
.map(|item| -> Vec<InlineKeyboardButton> {
|
||||
vec![InlineKeyboardButton {
|
||||
text: {
|
||||
format!("📥 {item}")
|
||||
},
|
||||
text: { format!("📥 {item}") },
|
||||
kind: InlineKeyboardButtonKind::CallbackData(
|
||||
(DownloadQueryData::DownloadData {
|
||||
book_id: book.id,
|
||||
@@ -264,14 +254,18 @@ async fn get_download_archive_keyboard_handler(
|
||||
bot: CacheMe<Throttle<Bot>>,
|
||||
command: DownloadArchiveCommand,
|
||||
) -> BotHandlerInternal {
|
||||
let allowed_langs = get_user_or_default_lang_codes(
|
||||
message.from().unwrap().id,
|
||||
).await;
|
||||
let allowed_langs = get_user_or_default_lang_codes(message.from().unwrap().id).await;
|
||||
|
||||
let available_types = match command {
|
||||
DownloadArchiveCommand::Sequence { id } => get_sequence_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,
|
||||
DownloadArchiveCommand::Sequence { id } => {
|
||||
get_sequence_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 {
|
||||
@@ -280,31 +274,39 @@ async fn get_download_archive_keyboard_handler(
|
||||
};
|
||||
|
||||
let keyboard = InlineKeyboardMarkup {
|
||||
inline_keyboard:
|
||||
available_types.iter()
|
||||
inline_keyboard: available_types
|
||||
.iter()
|
||||
.filter(|file_type| !file_type.contains("zip"))
|
||||
.map(|file_type| {
|
||||
let callback_data: String = match command {
|
||||
DownloadArchiveCommand::Sequence { id } => DownloadArchiveQueryData::Sequence {
|
||||
id, file_type: file_type.to_string()
|
||||
}.to_string(),
|
||||
id,
|
||||
file_type: file_type.to_string(),
|
||||
}
|
||||
.to_string(),
|
||||
DownloadArchiveCommand::Author { id } => DownloadArchiveQueryData::Author {
|
||||
id, file_type: file_type.to_string()
|
||||
}.to_string(),
|
||||
DownloadArchiveCommand::Translator { id } => DownloadArchiveQueryData::Translator {
|
||||
id, file_type: file_type.to_string()
|
||||
}.to_string(),
|
||||
id,
|
||||
file_type: file_type.to_string(),
|
||||
}
|
||||
.to_string(),
|
||||
DownloadArchiveCommand::Translator { id } => {
|
||||
DownloadArchiveQueryData::Translator {
|
||||
id,
|
||||
file_type: file_type.to_string(),
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
};
|
||||
|
||||
vec![InlineKeyboardButton {
|
||||
text: file_type.to_string(),
|
||||
kind: InlineKeyboardButtonKind::CallbackData(callback_data)
|
||||
kind: InlineKeyboardButtonKind::CallbackData(callback_data),
|
||||
}]
|
||||
}).collect()
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
bot
|
||||
.send_message(message.chat.id, "Выбери формат:")
|
||||
bot.send_message(message.chat.id, "Выбери формат:")
|
||||
.reply_markup(keyboard)
|
||||
.reply_to_message_id(message.id)
|
||||
.await?;
|
||||
@@ -327,15 +329,14 @@ async fn send_archive_link(
|
||||
message: Message,
|
||||
task: Task,
|
||||
) -> BotHandlerInternal {
|
||||
bot
|
||||
.edit_message_text(
|
||||
bot.edit_message_text(
|
||||
message.chat.id,
|
||||
message.id,
|
||||
format!(
|
||||
"Файл не может быть загружен в чат! \n \
|
||||
Вы можете скачать его <a href=\"{}\">по ссылке</a> (работает 3 часа)",
|
||||
task.result_link.unwrap()
|
||||
)
|
||||
),
|
||||
)
|
||||
.parse_mode(ParseMode::Html)
|
||||
.reply_markup(InlineKeyboardMarkup {
|
||||
@@ -362,7 +363,7 @@ async fn wait_archive(
|
||||
send_error_message(bot, message.chat.id, message.id).await;
|
||||
log::error!("{:?}", err);
|
||||
return Err(err);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if task.status != TaskStatus::InProgress {
|
||||
@@ -371,14 +372,13 @@ async fn wait_archive(
|
||||
|
||||
let now = Utc::now().format("%H:%M:%S UTC").to_string();
|
||||
|
||||
bot
|
||||
.edit_message_text(
|
||||
bot.edit_message_text(
|
||||
message.chat.id,
|
||||
message.id,
|
||||
format!(
|
||||
"Статус: \n ⏳ {} \n\nОбновлено в {now}",
|
||||
task.status_description
|
||||
)
|
||||
),
|
||||
)
|
||||
.reply_markup(get_check_keyboard(task.id))
|
||||
.send()
|
||||
@@ -394,53 +394,52 @@ async fn wait_archive(
|
||||
|
||||
if content_size > 20 * 1024 * 1024 {
|
||||
send_archive_link(bot.clone(), message.clone(), task.clone()).await?;
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let downloaded_data = match download_file_by_link(
|
||||
task.clone().result_filename.unwrap(),
|
||||
task.result_link.clone().unwrap()
|
||||
).await {
|
||||
task.result_link.clone().unwrap(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
send_error_message(bot, message.chat.id, message.id).await;
|
||||
log::error!("{:?}", err);
|
||||
return Err(err);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
match _send_downloaded_file(
|
||||
&message,
|
||||
bot.clone(),
|
||||
downloaded_data,
|
||||
).await {
|
||||
match _send_downloaded_file(&message, bot.clone(), downloaded_data).await {
|
||||
Ok(_) => (),
|
||||
Err(_) => {
|
||||
send_archive_link(bot.clone(), message.clone(), task).await?;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
bot
|
||||
.delete_message(message.chat.id, message.id)
|
||||
.await?;
|
||||
bot.delete_message(message.chat.id, message.id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
async fn download_archive(
|
||||
cq: CallbackQuery,
|
||||
download_archive_query_data: DownloadArchiveQueryData,
|
||||
bot: CacheMe<Throttle<Bot>>,
|
||||
) -> BotHandlerInternal {
|
||||
let allowed_langs = get_user_or_default_lang_codes(
|
||||
cq.from.id,
|
||||
).await;
|
||||
let allowed_langs = get_user_or_default_lang_codes(cq.from.id).await;
|
||||
|
||||
let (id, file_type, task_type) = match download_archive_query_data {
|
||||
DownloadArchiveQueryData::Sequence { id, file_type } => (id, file_type, TaskObjectType::Sequence),
|
||||
DownloadArchiveQueryData::Author { id, file_type } => (id, file_type, TaskObjectType::Author),
|
||||
DownloadArchiveQueryData::Translator { id, file_type } => (id, file_type, TaskObjectType::Translator),
|
||||
DownloadArchiveQueryData::Sequence { id, file_type } => {
|
||||
(id, file_type, TaskObjectType::Sequence)
|
||||
}
|
||||
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();
|
||||
@@ -450,7 +449,8 @@ async fn download_archive(
|
||||
object_type: task_type,
|
||||
file_format: file_type,
|
||||
allowed_langs,
|
||||
}).await;
|
||||
})
|
||||
.await;
|
||||
|
||||
let task = match task {
|
||||
Ok(v) => v,
|
||||
@@ -458,11 +458,10 @@ async fn download_archive(
|
||||
send_error_message(bot, message.chat.id, message.id).await;
|
||||
log::error!("{:?}", err);
|
||||
return Err(err);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
bot
|
||||
.edit_message_text(message.chat.id, message.id, "⏳ Подготовка архива...")
|
||||
bot.edit_message_text(message.chat.id, message.id, "⏳ Подготовка архива...")
|
||||
.reply_markup(get_check_keyboard(task.id.clone()))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use teloxide::utils::command::BotCommands;
|
||||
|
||||
|
||||
#[derive(BotCommands, Clone)]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
pub enum HelpCommand {
|
||||
|
||||
@@ -2,11 +2,14 @@ pub mod commands;
|
||||
|
||||
use crate::bots::BotHandlerInternal;
|
||||
|
||||
use teloxide::{prelude::*, types::ParseMode, adaptors::{Throttle, CacheMe}};
|
||||
use teloxide::{
|
||||
adaptors::{CacheMe, Throttle},
|
||||
prelude::*,
|
||||
types::ParseMode,
|
||||
};
|
||||
|
||||
use self::commands::HelpCommand;
|
||||
|
||||
|
||||
pub async fn help_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal {
|
||||
let name = message
|
||||
.from()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use strum_macros::{EnumIter, Display};
|
||||
|
||||
use strum_macros::{Display, EnumIter};
|
||||
|
||||
#[derive(Clone, Display, EnumIter)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
|
||||
@@ -1,29 +1,32 @@
|
||||
pub mod commands;
|
||||
pub mod callback_data;
|
||||
pub mod commands;
|
||||
|
||||
use smartstring::alias::String as SmartString;
|
||||
use smallvec::SmallVec;
|
||||
use smartstring::alias::String as SmartString;
|
||||
use teloxide::{
|
||||
adaptors::{CacheMe, Throttle},
|
||||
prelude::*,
|
||||
types::{InlineKeyboardButton, InlineKeyboardMarkup},
|
||||
adaptors::{Throttle, CacheMe},
|
||||
};
|
||||
|
||||
use crate::bots::{
|
||||
approved_bot::{
|
||||
modules::random::callback_data::RandomCallbackData,
|
||||
services::{
|
||||
book_library::{self, formatters::Format},
|
||||
user_settings::get_user_or_default_lang_codes,
|
||||
},
|
||||
tools::filter_callback_query, modules::random::callback_data::RandomCallbackData,
|
||||
tools::filter_callback_query,
|
||||
},
|
||||
BotHandlerInternal,
|
||||
};
|
||||
|
||||
use self::commands::RandomCommand;
|
||||
|
||||
|
||||
async fn random_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> crate::bots::BotHandlerInternal {
|
||||
async fn random_handler(
|
||||
message: Message,
|
||||
bot: CacheMe<Throttle<Bot>>,
|
||||
) -> crate::bots::BotHandlerInternal {
|
||||
const MESSAGE_TEXT: &str = "Что хотим получить?";
|
||||
|
||||
let keyboard = InlineKeyboardMarkup {
|
||||
@@ -55,8 +58,7 @@ async fn random_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> crate:
|
||||
],
|
||||
};
|
||||
|
||||
bot
|
||||
.send_message(message.chat.id, MESSAGE_TEXT)
|
||||
bot.send_message(message.chat.id, MESSAGE_TEXT)
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
@@ -76,12 +78,11 @@ where
|
||||
let item = match item {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
bot
|
||||
.send_message(cq.from.id, "Ошибка! Попробуйте позже :(")
|
||||
bot.send_message(cq.from.id, "Ошибка! Попробуйте позже :(")
|
||||
.send()
|
||||
.await?;
|
||||
return Err(err);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let item_message = item.format(4096).result;
|
||||
@@ -89,9 +90,7 @@ where
|
||||
bot.send_message(cq.from.id, item_message)
|
||||
.reply_markup(InlineKeyboardMarkup {
|
||||
inline_keyboard: vec![vec![InlineKeyboardButton {
|
||||
kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(
|
||||
cq.data.unwrap(),
|
||||
),
|
||||
kind: teloxide::types::InlineKeyboardButtonKind::CallbackData(cq.data.unwrap()),
|
||||
text: String::from("Повторить?"),
|
||||
}]],
|
||||
})
|
||||
@@ -107,7 +106,7 @@ where
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
@@ -128,18 +127,20 @@ where
|
||||
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 message = match cq.message {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
bot
|
||||
.send_message(cq.from.id, "Ошибка! Начните заново :(")
|
||||
bot.send_message(cq.from.id, "Ошибка! Начните заново :(")
|
||||
.send()
|
||||
.await?;
|
||||
return Ok(());
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let keyboard = InlineKeyboardMarkup {
|
||||
@@ -160,8 +161,7 @@ async fn get_genre_metas_handler(cq: CallbackQuery, bot: CacheMe<Throttle<Bot>>)
|
||||
.collect(),
|
||||
};
|
||||
|
||||
bot
|
||||
.edit_message_reply_markup(message.chat.id, message.id)
|
||||
bot.edit_message_reply_markup(message.chat.id, message.id)
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
.await?;
|
||||
@@ -179,8 +179,7 @@ async fn get_genres_by_meta_handler(
|
||||
let meta = match genre_metas.get(genre_index as usize) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
bot
|
||||
.send_message(cq.from.id, "Ошибка! Попробуйте позже :(")
|
||||
bot.send_message(cq.from.id, "Ошибка! Попробуйте позже :(")
|
||||
.send()
|
||||
.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
|
||||
.into_iter()
|
||||
.map(|genre| {
|
||||
@@ -217,8 +217,7 @@ async fn get_genres_by_meta_handler(
|
||||
let message = match cq.message {
|
||||
Some(message) => message,
|
||||
None => {
|
||||
bot
|
||||
.send_message(cq.from.id, "Ошибка! Начните заново :(")
|
||||
bot.send_message(cq.from.id, "Ошибка! Начните заново :(")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
@@ -226,8 +225,7 @@ async fn get_genres_by_meta_handler(
|
||||
}
|
||||
};
|
||||
|
||||
bot
|
||||
.edit_message_reply_markup(message.chat.id, message.id)
|
||||
bot.edit_message_reply_markup(message.chat.id, message.id)
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
.await?;
|
||||
@@ -249,30 +247,46 @@ async fn get_random_book_by_genre(
|
||||
|
||||
pub fn get_random_handler() -> crate::bots::BotHandler {
|
||||
dptree::entry()
|
||||
.branch(
|
||||
Update::filter_message()
|
||||
.branch(
|
||||
dptree::entry()
|
||||
.filter_command::<RandomCommand>()
|
||||
.endpoint(|message, command, bot| async {
|
||||
.branch(Update::filter_message().branch(
|
||||
dptree::entry().filter_command::<RandomCommand>().endpoint(
|
||||
|message, command, bot| async {
|
||||
match command {
|
||||
RandomCommand::Random => random_handler(message, bot).await,
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
},
|
||||
),
|
||||
))
|
||||
.branch(
|
||||
Update::filter_callback_query()
|
||||
.chain(filter_callback_query::<RandomCallbackData>())
|
||||
.endpoint(|cq: CallbackQuery, callback_data: RandomCallbackData, bot: CacheMe<Throttle<Bot>>| async move {
|
||||
.endpoint(
|
||||
|cq: CallbackQuery,
|
||||
callback_data: RandomCallbackData,
|
||||
bot: CacheMe<Throttle<Bot>>| async move {
|
||||
match callback_data {
|
||||
RandomCallbackData::RandomBook => get_random_item_handler(cq, bot, book_library::get_random_book).await,
|
||||
RandomCallbackData::RandomAuthor => get_random_item_handler(cq, bot, book_library::get_random_author).await,
|
||||
RandomCallbackData::RandomSequence => get_random_item_handler(cq, bot, book_library::get_random_sequence).await,
|
||||
RandomCallbackData::RandomBookByGenreRequest => get_genre_metas_handler(cq, bot).await,
|
||||
RandomCallbackData::Genres { index } => get_genres_by_meta_handler(cq, bot, index).await,
|
||||
RandomCallbackData::RandomBookByGenre { id } => get_random_book_by_genre(cq, bot, id).await,
|
||||
RandomCallbackData::RandomBook => {
|
||||
get_random_item_handler(cq, bot, book_library::get_random_book)
|
||||
.await
|
||||
}
|
||||
})
|
||||
RandomCallbackData::RandomAuthor => {
|
||||
get_random_item_handler(cq, bot, book_library::get_random_author)
|
||||
.await
|
||||
}
|
||||
RandomCallbackData::RandomSequence => {
|
||||
get_random_item_handler(cq, bot, book_library::get_random_sequence)
|
||||
.await
|
||||
}
|
||||
RandomCallbackData::RandomBookByGenreRequest => {
|
||||
get_genre_metas_handler(cq, bot).await
|
||||
}
|
||||
RandomCallbackData::Genres { index } => {
|
||||
get_genres_by_meta_handler(cq, bot, index).await
|
||||
}
|
||||
RandomCallbackData::RandomBookByGenre { id } => {
|
||||
get_random_book_by_genre(cq, bot, id).await
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use strum_macros::EnumIter;
|
||||
|
||||
use crate::bots::approved_bot::modules::utils::pagination::GetPaginationCallbackData;
|
||||
|
||||
|
||||
#[derive(Clone, EnumIter)]
|
||||
pub enum SearchCallbackData {
|
||||
Book { page: u32 },
|
||||
@@ -56,12 +55,8 @@ impl FromStr for SearchCallbackData {
|
||||
impl GetPaginationCallbackData for SearchCallbackData {
|
||||
fn get_pagination_callback_data(&self, target_page: u32) -> String {
|
||||
match self {
|
||||
SearchCallbackData::Book { .. } => {
|
||||
SearchCallbackData::Book { page: target_page }
|
||||
}
|
||||
SearchCallbackData::Authors { .. } => {
|
||||
SearchCallbackData::Authors { page: target_page }
|
||||
}
|
||||
SearchCallbackData::Book { .. } => SearchCallbackData::Book { page: target_page },
|
||||
SearchCallbackData::Authors { .. } => SearchCallbackData::Authors { page: target_page },
|
||||
SearchCallbackData::Sequences { .. } => {
|
||||
SearchCallbackData::Sequences { page: target_page }
|
||||
}
|
||||
|
||||
@@ -6,15 +6,18 @@ use smartstring::alias::String as SmartString;
|
||||
|
||||
use smallvec::SmallVec;
|
||||
use teloxide::{
|
||||
adaptors::{CacheMe, Throttle},
|
||||
dispatching::dialogue::GetChatId,
|
||||
prelude::*,
|
||||
types::{InlineKeyboardButton, InlineKeyboardMarkup}, dispatching::dialogue::GetChatId, adaptors::{Throttle, CacheMe},
|
||||
types::{InlineKeyboardButton, InlineKeyboardMarkup},
|
||||
};
|
||||
|
||||
use crate::bots::{
|
||||
approved_bot::{
|
||||
services::{
|
||||
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,
|
||||
},
|
||||
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;
|
||||
|
||||
|
||||
async fn generic_search_pagination_handler<T, P, Fut>(
|
||||
cq: CallbackQuery,
|
||||
bot: CacheMe<Throttle<Bot>>,
|
||||
@@ -46,11 +48,11 @@ where
|
||||
let query = get_query(cq);
|
||||
|
||||
let (chat_id, query, message_id) = match (chat_id, query, message_id) {
|
||||
(Some(chat_id), Some(query), Some(message_id)) => {
|
||||
(chat_id, query, message_id)
|
||||
}
|
||||
(Some(chat_id), Some(query), Some(message_id)) => (chat_id, query, message_id),
|
||||
(Some(chat_id), _, _) => {
|
||||
bot.send_message(chat_id, "Повторите поиск сначала").send().await?;
|
||||
bot.send_message(chat_id, "Повторите поиск сначала")
|
||||
.send()
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
@@ -70,8 +72,7 @@ where
|
||||
let mut items_page = match items_getter(query.clone(), page, allowed_langs.clone()).await {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
bot
|
||||
.send_message(chat_id, "Ошибка! Попробуйте позже :(")
|
||||
bot.send_message(chat_id, "Ошибка! Попробуйте позже :(")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
@@ -92,17 +93,11 @@ where
|
||||
};
|
||||
|
||||
if page > items_page.pages {
|
||||
items_page = match items_getter(
|
||||
query.clone(),
|
||||
items_page.pages,
|
||||
allowed_langs.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
items_page =
|
||||
match items_getter(query.clone(), items_page.pages, allowed_langs.clone()).await {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
bot
|
||||
.send_message(chat_id, "Ошибка! Попробуйте позже :(")
|
||||
bot.send_message(chat_id, "Ошибка! Попробуйте позже :(")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
@@ -115,8 +110,7 @@ where
|
||||
|
||||
let keyboard = generic_get_pagination_keyboard(page, items_page.pages, search_data, true);
|
||||
|
||||
bot
|
||||
.edit_message_text(chat_id, message_id, formated_page)
|
||||
bot.edit_message_text(chat_id, message_id, formated_page)
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
.await?;
|
||||
@@ -156,8 +150,7 @@ pub async fn message_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> B
|
||||
],
|
||||
};
|
||||
|
||||
bot
|
||||
.send_message(message.chat.id, message_text)
|
||||
bot.send_message(message.chat.id, message_text)
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_markup(keyboard)
|
||||
.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 {
|
||||
dptree::entry().branch(
|
||||
dptree::entry()
|
||||
.branch(
|
||||
Update::filter_message()
|
||||
.endpoint(|message, bot| async move { message_handler(message, bot).await }),
|
||||
).branch(
|
||||
)
|
||||
.branch(
|
||||
Update::filter_callback_query()
|
||||
.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 {
|
||||
SearchCallbackData::Book { .. } => generic_search_pagination_handler(cq, 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,
|
||||
SearchCallbackData::Book { .. } => {
|
||||
generic_search_pagination_handler(
|
||||
cq,
|
||||
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
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use teloxide::types::CallbackQuery;
|
||||
|
||||
|
||||
pub fn get_query(cq: CallbackQuery) -> Option<String> {
|
||||
cq.message
|
||||
.map(|message| {
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::str::FromStr;
|
||||
use regex::Regex;
|
||||
use smartstring::alias::String as SmartString;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum SettingsCallbackData {
|
||||
Settings,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use teloxide::macros::BotCommands;
|
||||
|
||||
|
||||
#[derive(BotCommands, Clone)]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
pub enum SettingsCommand {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod commands;
|
||||
pub mod callback_data;
|
||||
pub mod commands;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
@@ -16,12 +16,12 @@ use crate::bots::{
|
||||
};
|
||||
|
||||
use teloxide::{
|
||||
adaptors::{CacheMe, Throttle},
|
||||
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 {
|
||||
let keyboard = InlineKeyboardMarkup {
|
||||
@@ -33,8 +33,7 @@ async fn settings_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotH
|
||||
}]],
|
||||
};
|
||||
|
||||
bot
|
||||
.send_message(message.chat.id, "Настройки")
|
||||
bot.send_message(message.chat.id, "Настройки")
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
.await?;
|
||||
@@ -42,7 +41,10 @@ async fn settings_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotH
|
||||
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
|
||||
.into_iter()
|
||||
.map(|lang| {
|
||||
@@ -78,9 +80,11 @@ async fn settings_callback_handler(
|
||||
let message = match cq.message {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
bot.send_message(cq.from.id, "Ошибка! Попробуйте заново(").send().await?;
|
||||
bot.send_message(cq.from.id, "Ошибка! Попробуйте заново(")
|
||||
.send()
|
||||
.await?;
|
||||
return Ok(());
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let user = cq.from;
|
||||
@@ -103,14 +107,13 @@ async fn settings_callback_handler(
|
||||
};
|
||||
|
||||
if allowed_langs_set.is_empty() {
|
||||
bot
|
||||
.answer_callback_query(cq.id)
|
||||
bot.answer_callback_query(cq.id)
|
||||
.text("Должен быть активен, хотя бы один язык!")
|
||||
.show_alert(true)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Err(err) = create_or_update_user_settings(
|
||||
@@ -121,23 +124,27 @@ async fn settings_callback_handler(
|
||||
me.username.clone().unwrap(),
|
||||
allowed_langs_set.clone().into_iter().collect(),
|
||||
)
|
||||
.await {
|
||||
bot.send_message(message.chat.id, "Ошибка! Попробуйте заново(").send().await?;
|
||||
.await
|
||||
{
|
||||
bot.send_message(message.chat.id, "Ошибка! Попробуйте заново(")
|
||||
.send()
|
||||
.await?;
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let all_langs = match get_langs().await {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
bot.send_message(message.chat.id, "Ошибка! Попробуйте заново(").send().await?;
|
||||
return Err(err)
|
||||
},
|
||||
bot.send_message(message.chat.id, "Ошибка! Попробуйте заново(")
|
||||
.send()
|
||||
.await?;
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
|
||||
let keyboard = get_lang_keyboard(all_langs, allowed_langs_set);
|
||||
|
||||
bot
|
||||
.edit_message_reply_markup(message.chat.id, message.id)
|
||||
bot.edit_message_reply_markup(message.chat.id, message.id)
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
@@ -1,30 +1,37 @@
|
||||
use crate::bots::BotHandlerInternal;
|
||||
|
||||
use teloxide::{
|
||||
adaptors::{CacheMe, Throttle},
|
||||
prelude::*,
|
||||
utils::command::BotCommands, adaptors::{Throttle, CacheMe},
|
||||
utils::command::BotCommands,
|
||||
};
|
||||
|
||||
#[derive(BotCommands, Clone)]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
enum SupportCommand {
|
||||
Support,
|
||||
Donate
|
||||
Donate,
|
||||
}
|
||||
|
||||
pub async fn support_command_handler(
|
||||
message: Message,
|
||||
bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal {
|
||||
|
||||
bot: CacheMe<Throttle<Bot>>,
|
||||
) -> BotHandlerInternal {
|
||||
let is_bot = message.from().unwrap().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 {
|
||||
&message.from().unwrap().first_name
|
||||
};
|
||||
|
||||
let message_text = format!("
|
||||
let message_text = format!(
|
||||
"
|
||||
Привет, {username}!
|
||||
|
||||
Этот бот существует благодаря пожертвованиям от наших пользователей.
|
||||
@@ -50,10 +57,10 @@ Bitcoin - BTC:
|
||||
|
||||
The Open Network - TON:
|
||||
<pre>UQA4MySrq_60b_VMlR6UEmc_0u-neAUTXdtv8oKr_i6uhQNd</pre>
|
||||
");
|
||||
"
|
||||
);
|
||||
|
||||
bot
|
||||
.send_message(message.chat.id, message_text)
|
||||
bot.send_message(message.chat.id, message_text)
|
||||
.parse_mode(teloxide::types::ParseMode::Html)
|
||||
.disable_web_page_preview(true)
|
||||
.await?;
|
||||
@@ -64,7 +71,9 @@ The Open Network - TON:
|
||||
pub fn get_support_handler() -> crate::bots::BotHandler {
|
||||
dptree::entry().branch(
|
||||
Update::filter_message().branch(
|
||||
dptree::entry().filter_command::<SupportCommand>().endpoint(support_command_handler),
|
||||
dptree::entry()
|
||||
.filter_command::<SupportCommand>()
|
||||
.endpoint(support_command_handler),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use regex::Regex;
|
||||
|
||||
use crate::bots::approved_bot::modules::utils::pagination::GetPaginationCallbackData;
|
||||
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct UpdateLogCallbackData {
|
||||
pub from: NaiveDate,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use teloxide::macros::BotCommands;
|
||||
|
||||
|
||||
#[derive(BotCommands, Clone)]
|
||||
#[command(rename_rule = "snake_case")]
|
||||
pub enum UpdateLogCommand {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod commands;
|
||||
pub mod callback_data;
|
||||
pub mod commands;
|
||||
|
||||
use chrono::{prelude::*, Duration};
|
||||
|
||||
@@ -9,16 +9,15 @@ use crate::bots::{
|
||||
};
|
||||
|
||||
use teloxide::{
|
||||
adaptors::{CacheMe, Throttle},
|
||||
prelude::*,
|
||||
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;
|
||||
|
||||
|
||||
async fn update_log_command(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal {
|
||||
let now = Utc::now().date_naive();
|
||||
let d3 = now - Duration::days(3);
|
||||
@@ -82,9 +81,11 @@ async fn update_log_pagination_handler(
|
||||
let message = match cq.message {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
bot.send_message(cq.from.id, "Ошибка! Попробуйте заново(").send().await?;
|
||||
return Ok(())
|
||||
},
|
||||
bot.send_message(cq.from.id, "Ошибка! Попробуйте заново(")
|
||||
.send()
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
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(
|
||||
update_callback_data.page,
|
||||
update_callback_data.from.format("%Y-%m-%d").to_string().into(),
|
||||
update_callback_data.to.format("%Y-%m-%d").to_string().into(),
|
||||
update_callback_data
|
||||
.from
|
||||
.format("%Y-%m-%d")
|
||||
.to_string()
|
||||
.into(),
|
||||
update_callback_data
|
||||
.to
|
||||
.format("%Y-%m-%d")
|
||||
.to_string()
|
||||
.into(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if items_page.pages == 0 {
|
||||
bot
|
||||
.send_message(message.chat.id, "Нет новых книг за этот период.")
|
||||
bot.send_message(message.chat.id, "Нет новых книг за этот период.")
|
||||
.send()
|
||||
.await?;
|
||||
return Ok(());
|
||||
@@ -110,9 +118,18 @@ async fn update_log_pagination_handler(
|
||||
if update_callback_data.page > items_page.pages {
|
||||
items_page = get_uploaded_books(
|
||||
items_page.pages,
|
||||
update_callback_data.from.format("%Y-%m-%d").to_string().into(),
|
||||
update_callback_data.to.format("%Y-%m-%d").to_string().into(),
|
||||
).await?;
|
||||
update_callback_data
|
||||
.from
|
||||
.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;
|
||||
@@ -123,8 +140,7 @@ async fn update_log_pagination_handler(
|
||||
let message_text = format!("{header}{formatted_page}");
|
||||
|
||||
let keyboard = generic_get_pagination_keyboard(page, total_pages, update_callback_data, true);
|
||||
bot
|
||||
.edit_message_text(message.chat.id, message.id, message_text)
|
||||
bot.edit_message_text(message.chat.id, message.id, message_text)
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::fmt;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CallbackQueryParseError;
|
||||
|
||||
@@ -12,7 +11,6 @@ impl fmt::Display for CallbackQueryParseError {
|
||||
|
||||
impl std::error::Error for CallbackQueryParseError {}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CommandParseError;
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ use teloxide::{dptree, prelude::*, types::*};
|
||||
|
||||
use super::errors::CommandParseError;
|
||||
|
||||
|
||||
pub trait CommandParse<T> {
|
||||
fn parse(s: &str, bot_name: &str) -> Result<T, CommandParseError>;
|
||||
}
|
||||
|
||||
|
||||
pub fn filter_command<Output>() -> crate::bots::BotHandler
|
||||
where
|
||||
Output: CommandParse<Output> + Send + Sync + 'static,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod errors;
|
||||
pub mod filter_command;
|
||||
pub mod pagination;
|
||||
pub mod split_text;
|
||||
pub mod filter_command;
|
||||
pub mod errors;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use teloxide::types::{InlineKeyboardButton, InlineKeyboardMarkup};
|
||||
|
||||
|
||||
pub enum PaginationDelta {
|
||||
OneMinus,
|
||||
OnePlus,
|
||||
@@ -12,7 +11,6 @@ pub trait GetPaginationCallbackData {
|
||||
fn get_pagination_callback_data(&self, target_page: u32) -> String;
|
||||
}
|
||||
|
||||
|
||||
pub fn generic_get_pagination_button<T>(
|
||||
target_page: u32,
|
||||
delta: PaginationDelta,
|
||||
@@ -36,7 +34,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn generic_get_pagination_keyboard<T>(
|
||||
page: u32,
|
||||
total_pages: u32,
|
||||
|
||||
@@ -26,7 +26,6 @@ pub fn split_text_to_chunks(text: &str, width: usize) -> Vec<String> {
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::bots::approved_bot::modules::utils::split_text::split_text_to_chunks;
|
||||
|
||||
@@ -19,7 +19,7 @@ pub enum TaskStatus {
|
||||
InProgress,
|
||||
Archiving,
|
||||
Complete,
|
||||
Failed
|
||||
Failed,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -38,7 +38,7 @@ pub struct Task {
|
||||
pub error_message: Option<String>,
|
||||
pub result_filename: Option<String>,
|
||||
pub result_link: Option<String>,
|
||||
pub content_size: Option<u64>
|
||||
pub content_size: Option<u64>,
|
||||
}
|
||||
|
||||
pub async fn create_task(
|
||||
|
||||
@@ -2,13 +2,12 @@ use base64::{engine::general_purpose, Engine};
|
||||
use reqwest::StatusCode;
|
||||
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};
|
||||
|
||||
pub mod types;
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct DownloadError {
|
||||
status_code: StatusCode,
|
||||
@@ -25,7 +24,10 @@ impl std::error::Error for DownloadError {}
|
||||
pub async fn get_cached_message(
|
||||
download_data: &DownloadQueryData,
|
||||
) -> 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 response = client
|
||||
@@ -48,9 +50,12 @@ pub async fn get_cached_message(
|
||||
}
|
||||
|
||||
pub async fn get_download_link(
|
||||
download_data: &DownloadQueryData
|
||||
download_data: &DownloadQueryData,
|
||||
) -> 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 response = client
|
||||
@@ -75,7 +80,10 @@ pub async fn get_download_link(
|
||||
pub async fn download_file(
|
||||
download_data: &DownloadQueryData,
|
||||
) -> 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()
|
||||
.get(format!(
|
||||
@@ -120,10 +128,9 @@ pub async fn download_file(
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
pub async fn download_file_by_link(
|
||||
filename: String,
|
||||
link: String
|
||||
link: String,
|
||||
) -> Result<DownloadFile, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let response = reqwest::Client::new()
|
||||
.get(link)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct CachedMessage {
|
||||
pub message_id: i32,
|
||||
@@ -15,5 +14,5 @@ pub struct DownloadFile {
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DownloadLink {
|
||||
pub link: String
|
||||
pub link: String,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
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::{
|
||||
Author, AuthorBook, Book, BookAuthor, BookGenre, SearchBook, Sequence, Translator,
|
||||
TranslatorBook, SequenceBook, BookTranslator, Empty,
|
||||
Author, AuthorBook, Book, BookAuthor, BookGenre, BookTranslator, Empty, SearchBook, Sequence,
|
||||
SequenceBook, Translator, TranslatorBook,
|
||||
};
|
||||
|
||||
const NO_LIMIT: usize = 4096;
|
||||
@@ -45,7 +47,7 @@ impl FormatTitle for BookAuthor {
|
||||
} = self;
|
||||
|
||||
if *id == 0 {
|
||||
return "".to_string()
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
let command = (DownloadArchiveCommand::Author { id: *id }).to_string();
|
||||
@@ -64,7 +66,7 @@ impl FormatTitle for BookTranslator {
|
||||
} = self;
|
||||
|
||||
if *id == 0 {
|
||||
return "".to_string()
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
let command = (DownloadArchiveCommand::Translator { id: *id }).to_string();
|
||||
@@ -78,7 +80,7 @@ impl FormatTitle for Sequence {
|
||||
let Sequence { id, name } = self;
|
||||
|
||||
if *id == 0 {
|
||||
return "".to_string()
|
||||
return "".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 {
|
||||
if count == 0 {
|
||||
return "".to_string()
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
match !authors.is_empty() {
|
||||
@@ -126,7 +128,11 @@ fn format_authors(authors: Vec<BookAuthor>, count: usize) -> String {
|
||||
.collect::<Vec<String>>()
|
||||
.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")
|
||||
}
|
||||
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 {
|
||||
if count == 0 {
|
||||
return "".to_string()
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
match !translators.is_empty() {
|
||||
@@ -146,7 +152,11 @@ fn format_translators(translators: Vec<BookTranslator>, count: usize) -> String
|
||||
.collect::<Vec<String>>()
|
||||
.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")
|
||||
}
|
||||
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 {
|
||||
if count == 0 {
|
||||
return "".to_string()
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
match !sequences.is_empty() {
|
||||
@@ -166,7 +176,11 @@ fn format_sequences(sequences: Vec<Sequence>, count: usize) -> String {
|
||||
.collect::<Vec<String>>()
|
||||
.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")
|
||||
}
|
||||
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 {
|
||||
if count == 0 {
|
||||
return "".to_string()
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
match !genres.is_empty() {
|
||||
@@ -186,7 +200,11 @@ fn format_genres(genres: Vec<BookGenre>, count: usize) -> String {
|
||||
.collect::<Vec<String>>()
|
||||
.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")
|
||||
}
|
||||
false => "".to_string(),
|
||||
@@ -216,7 +234,7 @@ impl Format for Author {
|
||||
FormatResult {
|
||||
result,
|
||||
current_size: result_len,
|
||||
max_size: result_len
|
||||
max_size: result_len,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,7 +252,7 @@ impl Format for Sequence {
|
||||
FormatResult {
|
||||
result,
|
||||
current_size: result_len,
|
||||
max_size: result_len
|
||||
max_size: result_len,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,7 +280,7 @@ impl Format for Translator {
|
||||
FormatResult {
|
||||
result,
|
||||
current_size: result_len,
|
||||
max_size: result_len
|
||||
max_size: result_len,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,7 +302,12 @@ impl FormatVectorsCounts {
|
||||
}
|
||||
|
||||
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 {
|
||||
genres -= 1;
|
||||
@@ -293,8 +316,8 @@ impl FormatVectorsCounts {
|
||||
authors,
|
||||
translators,
|
||||
sequences,
|
||||
genres
|
||||
}
|
||||
genres,
|
||||
};
|
||||
}
|
||||
|
||||
if sequences > 0 {
|
||||
@@ -304,8 +327,8 @@ impl FormatVectorsCounts {
|
||||
authors,
|
||||
translators,
|
||||
sequences,
|
||||
genres
|
||||
}
|
||||
genres,
|
||||
};
|
||||
}
|
||||
|
||||
if translators > 0 {
|
||||
@@ -315,8 +338,8 @@ impl FormatVectorsCounts {
|
||||
authors,
|
||||
translators,
|
||||
sequences,
|
||||
genres
|
||||
}
|
||||
genres,
|
||||
};
|
||||
}
|
||||
|
||||
if authors > 0 {
|
||||
@@ -326,15 +349,15 @@ impl FormatVectorsCounts {
|
||||
authors,
|
||||
translators,
|
||||
sequences,
|
||||
genres
|
||||
}
|
||||
genres,
|
||||
};
|
||||
}
|
||||
|
||||
Self {
|
||||
authors,
|
||||
translators,
|
||||
sequences,
|
||||
genres
|
||||
genres,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,14 +377,20 @@ impl FormatVectorsResult {
|
||||
}
|
||||
|
||||
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 {
|
||||
authors,
|
||||
translators,
|
||||
sequences,
|
||||
genres,
|
||||
max_result_size
|
||||
max_result_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,7 +401,7 @@ impl Book {
|
||||
authors: self.authors.len(),
|
||||
translators: self.translators.len(),
|
||||
sequences: self.sequences.len(),
|
||||
genres: self.genres.len()
|
||||
genres: self.genres.len(),
|
||||
};
|
||||
|
||||
let mut result = FormatVectorsResult {
|
||||
@@ -380,7 +409,7 @@ impl Book {
|
||||
translators: format_translators(self.translators.clone(), counts.translators),
|
||||
sequences: format_sequences(self.sequences.clone(), counts.sequences),
|
||||
genres: format_genres(self.genres.clone(), counts.genres),
|
||||
max_result_size: 0
|
||||
max_result_size: 0,
|
||||
};
|
||||
|
||||
let max_result_size = result.len();
|
||||
@@ -393,7 +422,7 @@ impl Book {
|
||||
translators: format_translators(self.translators.clone(), counts.translators),
|
||||
sequences: format_sequences(self.sequences.clone(), counts.sequences),
|
||||
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 required_data_len: usize = format!("{book_title}{annotations}{download_links}").len();
|
||||
let FormatVectorsResult { authors, translators, sequences, genres, max_result_size } = self.format_vectors(
|
||||
max_size - required_data_len
|
||||
);
|
||||
let FormatVectorsResult {
|
||||
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();
|
||||
|
||||
FormatResult {
|
||||
result,
|
||||
current_size: result_len,
|
||||
max_size: max_result_size + required_data_len
|
||||
max_size: max_result_size + required_data_len,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ use crate::config;
|
||||
|
||||
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
|
||||
.into_iter()
|
||||
.map(|lang| ("allowed_langs", lang))
|
||||
@@ -38,13 +40,11 @@ where
|
||||
Err(err) => {
|
||||
log::error!("Failed serialization: url={:?} err={:?}", url, err);
|
||||
Err(Box::new(err))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_book(
|
||||
id: u32,
|
||||
) -> Result<types::Book , Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub async fn get_book(id: u32) -> Result<types::Book, Box<dyn std::error::Error + Send + Sync>> {
|
||||
_make_request(&format!("/api/v1/books/{id}"), vec![]).await
|
||||
}
|
||||
|
||||
@@ -169,7 +169,10 @@ pub async fn get_author_books(
|
||||
id: u32,
|
||||
page: u32,
|
||||
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);
|
||||
|
||||
params.push(("page", page.to_string().into()));
|
||||
@@ -182,7 +185,10 @@ pub async fn get_translator_books(
|
||||
id: u32,
|
||||
page: u32,
|
||||
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);
|
||||
|
||||
params.push(("page", page.to_string().into()));
|
||||
@@ -195,7 +201,10 @@ pub async fn get_sequence_books(
|
||||
id: u32,
|
||||
page: u32,
|
||||
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);
|
||||
|
||||
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>> {
|
||||
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(
|
||||
@@ -235,14 +248,22 @@ pub async fn get_translator_books_available_types(
|
||||
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
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(
|
||||
id: u32,
|
||||
allowed_langs: SmallVec<[SmartString; 3]>
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use smallvec::SmallVec;
|
||||
|
||||
use super::formatters::{Format, FormatResult, FormatTitle};
|
||||
|
||||
|
||||
#[derive(Default, Deserialize, Debug, Clone)]
|
||||
pub struct BookAuthor {
|
||||
pub id: u32,
|
||||
@@ -87,13 +86,13 @@ pub struct Page<T, P> {
|
||||
pub pages: u32,
|
||||
|
||||
#[serde(default)]
|
||||
pub parent_item: Option<P>
|
||||
pub parent_item: Option<P>,
|
||||
}
|
||||
|
||||
impl<T, P> Page<T, P>
|
||||
where
|
||||
T: Format + Clone + Debug,
|
||||
P: FormatTitle + Clone + Debug
|
||||
P: FormatTitle + Clone + Debug,
|
||||
{
|
||||
pub fn format(&self, page: u32, max_size: usize) -> String {
|
||||
let title: String = match &self.parent_item {
|
||||
@@ -105,7 +104,7 @@ where
|
||||
}
|
||||
|
||||
format!("{item_title}\n\n\n")
|
||||
},
|
||||
}
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
@@ -124,7 +123,8 @@ where
|
||||
let items_count: usize = self.items.len();
|
||||
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()
|
||||
.map(|item| item.format(item_size))
|
||||
.collect();
|
||||
@@ -232,7 +232,7 @@ impl From<SearchBook> for Book {
|
||||
translators: value.translators,
|
||||
sequences: value.sequences,
|
||||
genres: vec![],
|
||||
pages: None
|
||||
pages: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,7 +262,7 @@ impl From<AuthorBook> for Book {
|
||||
translators: value.translators,
|
||||
sequences: value.sequences,
|
||||
genres: vec![],
|
||||
pages: None
|
||||
pages: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,12 +292,11 @@ impl From<TranslatorBook> for Book {
|
||||
translators: vec![],
|
||||
sequences: value.sequences,
|
||||
genres: vec![],
|
||||
pages: None
|
||||
pages: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct SequenceBook {
|
||||
pub id: u32,
|
||||
@@ -323,7 +322,7 @@ impl From<SequenceBook> for Book {
|
||||
translators: value.translators,
|
||||
sequences: vec![],
|
||||
genres: vec![],
|
||||
pages: None
|
||||
pages: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
pub async fn send_donation_notification(
|
||||
bot: CacheMe<Throttle<Bot>>,
|
||||
message: Message,
|
||||
) -> 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(());
|
||||
} 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(());
|
||||
}
|
||||
|
||||
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?;
|
||||
|
||||
support_command_handler(message, bot).await?;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod batch_downloader;
|
||||
pub mod book_cache;
|
||||
pub mod book_library;
|
||||
pub mod user_settings;
|
||||
pub mod donation_notifications;
|
||||
pub mod batch_downloader;
|
||||
pub mod user_settings;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use teloxide::types::{UserId, ChatId};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
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)]
|
||||
pub struct Lang {
|
||||
@@ -40,25 +40,20 @@ pub async fn get_user_settings(
|
||||
Ok(response.json::<UserSettings>().await?)
|
||||
}
|
||||
|
||||
pub async fn get_user_or_default_lang_codes(
|
||||
user_id: UserId,
|
||||
) -> SmallVec<[SmartString; 3]> {
|
||||
pub async fn get_user_or_default_lang_codes(user_id: UserId) -> SmallVec<[SmartString; 3]> {
|
||||
if let Some(cached_langs) = USER_LANGS_CACHE.get(&user_id).await {
|
||||
return cached_langs;
|
||||
}
|
||||
|
||||
let default_lang_codes = smallvec![
|
||||
"ru".into(),
|
||||
"be".into(),
|
||||
"uk".into()
|
||||
];
|
||||
let default_lang_codes = smallvec!["ru".into(), "be".into(), "uk".into()];
|
||||
|
||||
match get_user_settings(user_id).await {
|
||||
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;
|
||||
langs
|
||||
},
|
||||
}
|
||||
Err(_) => default_lang_codes,
|
||||
}
|
||||
}
|
||||
@@ -109,7 +104,10 @@ pub async fn update_user_activity(
|
||||
user_id: UserId,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
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)
|
||||
.send()
|
||||
.await?
|
||||
@@ -118,9 +116,14 @@ pub async fn update_user_activity(
|
||||
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()
|
||||
.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)
|
||||
.send()
|
||||
.await?
|
||||
@@ -129,9 +132,14 @@ pub async fn is_need_donate_notifications(chat_id: ChatId) -> Result<bool, Box<d
|
||||
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()
|
||||
.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)
|
||||
.send()
|
||||
.await?
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use teloxide::{dptree, types::CallbackQuery};
|
||||
|
||||
|
||||
pub fn filter_callback_query<T>() -> crate::bots::BotHandler
|
||||
where
|
||||
T: std::str::FromStr + Send + Sync + 'static,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use teloxide::{prelude::*, adaptors::{Throttle, CacheMe}};
|
||||
use teloxide::{
|
||||
adaptors::{CacheMe, Throttle},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
@@ -23,7 +26,6 @@ pub async fn message_handler(
|
||||
register::RegisterStatus::RegisterFail => strings::ALREADY_REGISTERED.to_string(),
|
||||
};
|
||||
|
||||
|
||||
bot.send_message(message.chat.id, message_text)
|
||||
.reply_to_message_id(message.id)
|
||||
.await?;
|
||||
@@ -39,6 +41,9 @@ pub fn get_manager_handler() -> Handler<
|
||||
> {
|
||||
Update::filter_message().branch(
|
||||
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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use tracing::log;
|
||||
|
||||
use crate::config;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RegisterStatus {
|
||||
Success { username: String },
|
||||
@@ -14,7 +13,6 @@ pub enum RegisterStatus {
|
||||
RegisterFail,
|
||||
}
|
||||
|
||||
|
||||
async fn get_bot_username(token: &str) -> Option<String> {
|
||||
match Bot::new(token).get_me().send().await {
|
||||
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!({
|
||||
"token": token,
|
||||
"user": user_id,
|
||||
@@ -46,13 +48,12 @@ async fn make_register_request(user_id: UserId, username: &str, token: &str) ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub async fn register(user_id: UserId, message_text: &str) -> RegisterStatus {
|
||||
let token = super::utils::get_token(message_text).unwrap();
|
||||
|
||||
let bot_username = match get_bot_username(token).await {
|
||||
Some(v) => v,
|
||||
None => return RegisterStatus::WrongToken
|
||||
None => return RegisterStatus::WrongToken,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
RegisterStatus::Success { username: bot_username }
|
||||
RegisterStatus::Success {
|
||||
username: bot_username,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
pub fn format_registered_message(username: &str) -> String {
|
||||
format!("@{username} зарегистрирован и через несколько минут будет подключен!", username = username)
|
||||
format!(
|
||||
"@{username} зарегистрирован и через несколько минут будет подключен!",
|
||||
username = username
|
||||
)
|
||||
}
|
||||
|
||||
pub const ALREADY_REGISTERED: &str = "Ошибка! Возможно бот уже зарегистрирован!";
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
use regex::Regex;
|
||||
|
||||
|
||||
pub fn get_token(message_text: &str) -> Option<&str> {
|
||||
let re = Regex::new("(?P<token>[0-9]+:[0-9a-zA-Z-_]+)").unwrap();
|
||||
|
||||
match re.find(message_text) {
|
||||
Some(v) => Some(v.as_str()),
|
||||
None => None
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -42,7 +40,10 @@ mod tests {
|
||||
|
||||
let result = get_token(message);
|
||||
|
||||
assert_eq!(result.unwrap(), "5555555555:AAF-AAAAAAAA1239AA2AAsvy13Axp23RAa");
|
||||
assert_eq!(
|
||||
result.unwrap(),
|
||||
"5555555555:AAF-AAAAAAAA1239AA2AAsvy13Axp23RAa"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -18,25 +18,14 @@ type BotCommands = Option<Vec<teloxide::types::BotCommand>>;
|
||||
|
||||
fn ignore_channel_messages() -> crate::bots::BotHandler {
|
||||
dptree::entry()
|
||||
.branch(
|
||||
Update::filter_channel_post()
|
||||
.endpoint(|| async { Ok(()) })
|
||||
).branch(
|
||||
Update::filter_edited_channel_post()
|
||||
.endpoint(|| async { Ok(()) })
|
||||
)
|
||||
.branch(Update::filter_channel_post().endpoint(|| async { Ok(()) }))
|
||||
.branch(Update::filter_edited_channel_post().endpoint(|| async { Ok(()) }))
|
||||
}
|
||||
|
||||
fn ignore_chat_member_update() -> crate::bots::BotHandler {
|
||||
dptree::entry()
|
||||
.branch(
|
||||
Update::filter_chat_member()
|
||||
.endpoint(|| async { Ok(()) })
|
||||
)
|
||||
.branch(
|
||||
Update::filter_my_chat_member()
|
||||
.endpoint(|| async { Ok(()) })
|
||||
)
|
||||
.branch(Update::filter_chat_member().endpoint(|| async { Ok(()) }))
|
||||
.branch(Update::filter_my_chat_member().endpoint(|| async { Ok(()) }))
|
||||
}
|
||||
|
||||
pub fn get_bot_handler() -> (BotHandler, BotCommands) {
|
||||
|
||||
@@ -2,7 +2,6 @@ use serde::Deserialize;
|
||||
|
||||
use crate::config;
|
||||
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||
pub enum BotCache {
|
||||
#[serde(rename = "original")]
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
pub mod bot_manager_client;
|
||||
|
||||
use axum::extract::{State, Path};
|
||||
use axum::extract::{Path, State};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::post;
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::StatusCode;
|
||||
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 tokio::sync::mpsc::{UnboundedSender, self};
|
||||
use tokio::sync::mpsc::{self, UnboundedSender};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
use tracing::log;
|
||||
use url::Url;
|
||||
@@ -24,7 +24,7 @@ use tokio::sync::RwLock;
|
||||
use smallvec::SmallVec;
|
||||
use teloxide::adaptors::throttle::Limits;
|
||||
use teloxide::types::{BotCommand, UpdateKind};
|
||||
use tokio::time::{sleep, Duration, self};
|
||||
use tokio::time::{self, sleep, Duration};
|
||||
use tower_http::trace::TraceLayer;
|
||||
|
||||
use teloxide::prelude::*;
|
||||
@@ -35,15 +35,12 @@ use self::bot_manager_client::get_bots;
|
||||
pub use self::bot_manager_client::{BotCache, BotData};
|
||||
use crate::config;
|
||||
|
||||
|
||||
type UpdateSender = mpsc::UnboundedSender<Result<Update, std::convert::Infallible>>;
|
||||
|
||||
|
||||
fn tuple_first_mut<A, B>(tuple: &mut (A, B)) -> &mut A {
|
||||
&mut tuple.0
|
||||
}
|
||||
|
||||
|
||||
pub static USER_ACTIVITY_CACHE: Lazy<Cache<UserId, ()>> = Lazy::new(|| {
|
||||
Cache::builder()
|
||||
.time_to_idle(Duration::from_secs(5 * 60))
|
||||
@@ -65,9 +62,17 @@ pub static CHAT_DONATION_NOTIFICATIONS_CACHE: Lazy<Cache<ChatId, ()>> = Lazy::ne
|
||||
.build()
|
||||
});
|
||||
|
||||
|
||||
type Routes = Arc<RwLock<HashMap<String, (StopToken, ClosableSender<Result<Update, std::convert::Infallible>>)>>>;
|
||||
|
||||
type Routes = Arc<
|
||||
RwLock<
|
||||
HashMap<
|
||||
String,
|
||||
(
|
||||
StopToken,
|
||||
ClosableSender<Result<Update, std::convert::Infallible>>,
|
||||
),
|
||||
>,
|
||||
>,
|
||||
>;
|
||||
|
||||
struct ClosableSender<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> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { origin: self.origin.clone() }
|
||||
Self {
|
||||
origin: self.origin.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ClosableSender<T> {
|
||||
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>> {
|
||||
@@ -93,7 +102,6 @@ impl<T> ClosableSender<T> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct ServerState {
|
||||
routers: Routes,
|
||||
@@ -102,7 +110,7 @@ struct ServerState {
|
||||
pub struct BotsManager {
|
||||
port: u16,
|
||||
|
||||
state: ServerState
|
||||
state: ServerState,
|
||||
}
|
||||
|
||||
impl BotsManager {
|
||||
@@ -111,12 +119,19 @@ impl BotsManager {
|
||||
port: 8000,
|
||||
|
||||
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 (stop_token, stop_flag) = mk_stop_token();
|
||||
@@ -126,9 +141,7 @@ impl BotsManager {
|
||||
let listener = StatefulListener::new(
|
||||
(stream, stop_token.clone()),
|
||||
tuple_first_mut,
|
||||
|state: &mut (_, StopToken)| {
|
||||
state.1.clone()
|
||||
},
|
||||
|state: &mut (_, StopToken)| state.1.clone(),
|
||||
);
|
||||
|
||||
(stop_token, stop_flag, tx, listener)
|
||||
@@ -202,7 +215,7 @@ impl BotsManager {
|
||||
self.start_bot(bot_data).await;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(err) => {
|
||||
log::info!("{:?}", err);
|
||||
}
|
||||
@@ -215,7 +228,6 @@ impl BotsManager {
|
||||
Path(token): Path<String>,
|
||||
input: String,
|
||||
) -> impl IntoResponse {
|
||||
|
||||
let routes = routers.read().await;
|
||||
let tx = routes.get(&token);
|
||||
|
||||
@@ -232,7 +244,7 @@ impl BotsManager {
|
||||
sender.close();
|
||||
};
|
||||
return StatusCode::SERVICE_UNAVAILABLE;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
match serde_json::from_str::<Update>(&input) {
|
||||
|
||||
@@ -61,6 +61,4 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub static CONFIG: Lazy<Config> = Lazy::new(|| {
|
||||
Config::load()
|
||||
});
|
||||
pub static CONFIG: Lazy<Config> = Lazy::new(Config::load);
|
||||
|
||||
@@ -2,15 +2,14 @@ use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use sentry::ClientOptions;
|
||||
use sentry::integrations::debug_images::DebugImagesIntegration;
|
||||
use sentry::types::Dsn;
|
||||
use sentry::ClientOptions;
|
||||
|
||||
mod bots;
|
||||
mod bots_manager;
|
||||
mod config;
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt()
|
||||
|
||||
Reference in New Issue
Block a user