Add pre-commit

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

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

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

View File

@@ -2,9 +2,16 @@ pub mod modules;
pub mod services;
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;
}

View File

@@ -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),
})
},
)
}
}

View File

@@ -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),
}
})?
}
}

View File

@@ -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 {

View File

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

View File

@@ -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(

View File

@@ -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),
}
},
)?
}
}

View File

@@ -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),
}
})?
}
}

View File

@@ -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?;

View File

@@ -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 {

View File

@@ -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),
}
}
}

View File

@@ -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?;

View File

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

View File

@@ -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()

View File

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

View File

@@ -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
}
}
},
),
)
}

View File

@@ -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 }
}

View File

@@ -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
}
}
},
),
)
}

View File

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

View File

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

View File

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

View File

@@ -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?;

View File

@@ -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),
),
)
}

View File

@@ -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,

View File

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

View File

@@ -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?;

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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(

View File

@@ -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)

View File

@@ -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,
}

View File

@@ -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,
}
}
}

View File

@@ -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
}

View File

@@ -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,
}
}
}

View File

@@ -1,22 +1,36 @@
use teloxide::{types::Message, adaptors::{CacheMe, Throttle}, Bot};
use teloxide::{
adaptors::{CacheMe, Throttle},
types::Message,
Bot,
};
use crate::{bots::{BotHandlerInternal, approved_bot::modules::support::support_command_handler}, bots_manager::CHAT_DONATION_NOTIFICATIONS_CACHE};
use crate::{
bots::{approved_bot::modules::support::support_command_handler, BotHandlerInternal},
bots_manager::CHAT_DONATION_NOTIFICATIONS_CACHE,
};
use super::user_settings::{is_need_donate_notifications, mark_donate_notification_sent};
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?;

View File

@@ -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;

View File

@@ -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?

View File

@@ -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,

View File

@@ -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),
)
}

View File

@@ -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,
}
}

View File

@@ -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 = "Ошибка! Возможно бот уже зарегистрирован!";

View File

@@ -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]

View File

@@ -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) {

View File

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

View File

@@ -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) {

View File

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

View File

@@ -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()