mirror of
https://github.com/flibusta-apps/book_bot.git
synced 2025-12-06 07:25:36 +01:00
Compare commits
19 Commits
ae292b180c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| fc10c4c576 | |||
| 359a6b6137 | |||
| 17ef8a7f3d | |||
| 0d028b6a66 | |||
| 9fb550404e | |||
| 07c725e0df | |||
|
|
6f2de597b4 | ||
| 6990f01275 | |||
| 7541680070 | |||
|
|
5a7caef4eb | ||
| 5ec0af8fd7 | |||
| fa56371148 | |||
|
|
c71ea486e5 | ||
| 61b0b68194 | |||
| 28892d9fea | |||
| 6b39a837ff | |||
| 9da1c4eed2 | |||
| 13612062ad | |||
| 34f0c9feb6 |
2
.github/workflows/build_docker_image.yml
vendored
2
.github/workflows/build_docker_image.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
55
.github/workflows/rust-clippy.yml
vendored
Normal file
55
.github/workflows/rust-clippy.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
# rust-clippy is a tool that runs a bunch of lints to catch common
|
||||
# mistakes in your Rust code and help improve your Rust code.
|
||||
# More details at https://github.com/rust-lang/rust-clippy
|
||||
# and https://rust-lang.github.io/rust-clippy/
|
||||
|
||||
name: rust-clippy analyze
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '38 20 * * 2'
|
||||
|
||||
jobs:
|
||||
rust-clippy-analyze:
|
||||
name: Run rust-clippy analyzing
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
override: true
|
||||
|
||||
- name: Install required cargo
|
||||
run: cargo install clippy-sarif sarif-fmt
|
||||
|
||||
- name: Run rust-clippy
|
||||
run:
|
||||
cargo clippy
|
||||
--all-features
|
||||
--message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload analysis results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
with:
|
||||
sarif_file: rust-clippy-results.sarif
|
||||
wait-for-processing: true
|
||||
399
Cargo.lock
generated
399
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -27,14 +27,14 @@ tokio-stream = "0.1.17"
|
||||
futures = "0.3.31"
|
||||
|
||||
axum = "0.8.3"
|
||||
axum-prometheus = "0.8.0"
|
||||
axum-prometheus = "0.9.0"
|
||||
|
||||
tower = "0.5.2"
|
||||
tower-http = { version = "0.6.2", features = ["trace"] }
|
||||
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
sentry-tracing = "0.41.0"
|
||||
sentry-tracing = "0.42.0"
|
||||
|
||||
reqwest = { version = "0.12.15", features = ["json", "stream"] }
|
||||
|
||||
@@ -66,5 +66,5 @@ smartstring = { version = "1.0.1", features = ["serde"] }
|
||||
|
||||
moka = { version = "0.12.10", features = ["future"] }
|
||||
|
||||
sentry = { version = "0.41.0", features = ["debug-images"] }
|
||||
sentry = { version = "0.42.0", features = ["debug-images"] }
|
||||
anyhow = "1.0.98"
|
||||
|
||||
@@ -41,10 +41,10 @@ async fn _update_activity(me: teloxide::types::Me, user: teloxide::types::User)
|
||||
|
||||
if create_or_update_user_settings(
|
||||
user.id,
|
||||
user.last_name.clone().unwrap_or("".to_string()),
|
||||
user.first_name.clone(),
|
||||
user.username.clone().unwrap_or("".to_string()),
|
||||
me.username.clone().unwrap(),
|
||||
&user.last_name.unwrap_or("".to_string()),
|
||||
&user.first_name,
|
||||
&user.username.unwrap_or("".to_string()),
|
||||
&me.username.clone().unwrap_or("".to_string()),
|
||||
allowed_langs,
|
||||
)
|
||||
.await
|
||||
@@ -64,21 +64,18 @@ async fn _update_activity(me: teloxide::types::Me, user: teloxide::types::User)
|
||||
|
||||
fn update_user_activity_handler() -> BotHandler {
|
||||
dptree::entry()
|
||||
.branch(
|
||||
Update::filter_callback_query().chain(dptree::filter_map_async(
|
||||
|cq: CallbackQuery, bot: CacheMe<Throttle<Bot>>| async move {
|
||||
_update_activity(bot.get_me().await.unwrap(), cq.from).await
|
||||
},
|
||||
)),
|
||||
)
|
||||
.branch(Update::filter_message().chain(dptree::filter_map_async(
|
||||
.branch(Update::filter_callback_query().inspect_async(
|
||||
|cq: CallbackQuery, bot: CacheMe<Throttle<Bot>>| async move {
|
||||
_update_activity(bot.get_me().await.unwrap(), cq.from).await;
|
||||
},
|
||||
))
|
||||
.branch(Update::filter_message().inspect_async(
|
||||
|message: Message, bot: CacheMe<Throttle<Bot>>| async move {
|
||||
match message.from {
|
||||
Some(user) => _update_activity(bot.get_me().await.unwrap(), user.clone()).await,
|
||||
None => None,
|
||||
if let Some(user) = message.from {
|
||||
_update_activity(bot.get_me().await.unwrap(), user).await;
|
||||
}
|
||||
},
|
||||
)))
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_approved_handler() -> (BotHandler, BotCommands) {
|
||||
|
||||
@@ -18,7 +18,9 @@ use tokio_util::compat::FuturesAsyncReadCompatExt;
|
||||
|
||||
use crate::bots::{
|
||||
approved_bot::{
|
||||
modules::utils::pagination::generic_get_pagination_keyboard,
|
||||
modules::utils::{
|
||||
message_text::is_message_text_equals, pagination::generic_get_pagination_keyboard,
|
||||
},
|
||||
services::book_library::{get_author_annotation, get_book_annotation},
|
||||
tools::filter_callback_query,
|
||||
},
|
||||
@@ -145,17 +147,24 @@ where
|
||||
} else {
|
||||
chunked_text.len()
|
||||
};
|
||||
let current_text = chunked_text.get(page_index - 1).unwrap();
|
||||
let new_text = chunked_text.get(page_index - 1).unwrap();
|
||||
|
||||
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)
|
||||
if is_message_text_equals(Some(message.clone()), new_text) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match bot
|
||||
.edit_message_text(message.chat().id, message.id(), new_text)
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_annotations_handler() -> crate::bots::BotHandler {
|
||||
|
||||
@@ -15,6 +15,7 @@ use teloxide::{
|
||||
use tracing::log;
|
||||
|
||||
use crate::bots::approved_bot::{
|
||||
modules::utils::message_text::is_message_text_equals,
|
||||
services::{
|
||||
book_library::{
|
||||
formatters::{Format, FormatTitle},
|
||||
@@ -66,7 +67,7 @@ where
|
||||
|
||||
let allowed_langs = get_user_or_default_lang_codes(user_id).await;
|
||||
|
||||
let items_page = match books_getter(id, 1, allowed_langs.clone()).await {
|
||||
let items_page = match books_getter(id, 1, allowed_langs).await {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
bot.send_message(chat_id, "Ошибка! Попробуйте позже :(")
|
||||
@@ -160,7 +161,7 @@ where
|
||||
};
|
||||
|
||||
if page > items_page.pages {
|
||||
items_page = match books_getter(id, items_page.pages, allowed_langs.clone()).await {
|
||||
items_page = match books_getter(id, items_page.pages, allowed_langs).await {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
bot.send_message(chat_id, "Ошибка! Попробуйте позже :(")
|
||||
@@ -176,12 +177,19 @@ where
|
||||
|
||||
let keyboard = generic_get_pagination_keyboard(page, items_page.pages, callback_data, true);
|
||||
|
||||
bot.edit_message_text(chat_id, message_id, formatted_page)
|
||||
if is_message_text_equals(cq.message, &formatted_page) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match bot
|
||||
.edit_message_text(chat_id, message_id, formatted_page)
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_book_handler() -> crate::bots::BotHandler {
|
||||
|
||||
@@ -97,12 +97,12 @@ async fn send_cached_message(
|
||||
|
||||
if _send_cached(&message, &bot, cached).await.is_ok() {
|
||||
if need_delete_message {
|
||||
if let MaybeInaccessibleMessage::Regular(message) = message.clone() {
|
||||
if let MaybeInaccessibleMessage::Regular(message) = &message {
|
||||
let _ = bot.delete_message(message.chat.id, message.id).await;
|
||||
}
|
||||
}
|
||||
|
||||
match send_donation_notification(bot.clone(), message).await {
|
||||
match send_donation_notification(&bot, &message).await {
|
||||
Ok(_) => (),
|
||||
Err(err) => log::error!("{err:?}"),
|
||||
}
|
||||
@@ -119,7 +119,7 @@ async fn send_cached_message(
|
||||
|
||||
async fn _send_downloaded_file(
|
||||
message: &MaybeInaccessibleMessage,
|
||||
bot: CacheMe<Throttle<Bot>>,
|
||||
bot: &CacheMe<Throttle<Bot>>,
|
||||
downloaded_data: DownloadFile,
|
||||
) -> BotHandlerInternal {
|
||||
let DownloadFile {
|
||||
@@ -134,14 +134,14 @@ async fn _send_downloaded_file(
|
||||
.into_async_read()
|
||||
.compat();
|
||||
|
||||
let document = InputFile::read(data).file_name(filename.clone());
|
||||
let document = InputFile::read(data).file_name(filename);
|
||||
|
||||
bot.send_document(message.chat().id, document)
|
||||
.caption(caption)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
send_donation_notification(bot, message.clone()).await?;
|
||||
send_donation_notification(bot, message).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -159,7 +159,7 @@ async fn send_with_download_from_channel(
|
||||
}
|
||||
};
|
||||
|
||||
_send_downloaded_file(&message, bot.clone(), downloaded_file).await?;
|
||||
_send_downloaded_file(&message, &bot, downloaded_file).await?;
|
||||
|
||||
if need_delete_message {
|
||||
if let MaybeInaccessibleMessage::Regular(message) = message {
|
||||
@@ -240,13 +240,13 @@ async fn get_download_archive_keyboard_handler(
|
||||
|
||||
let available_types = match command {
|
||||
DownloadArchiveCommand::Sequence { id } => {
|
||||
get_sequence_books_available_types(id, allowed_langs).await
|
||||
get_sequence_books_available_types(id, &allowed_langs).await
|
||||
}
|
||||
DownloadArchiveCommand::Author { id } => {
|
||||
get_author_books_available_types(id, allowed_langs).await
|
||||
get_author_books_available_types(id, &allowed_langs).await
|
||||
}
|
||||
DownloadArchiveCommand::Translator { id } => {
|
||||
get_translator_books_available_types(id, allowed_langs).await
|
||||
get_translator_books_available_types(id, &allowed_langs).await
|
||||
}
|
||||
};
|
||||
|
||||
@@ -307,19 +307,20 @@ async fn send_error_message(bot: CacheMe<Throttle<Bot>>, chat_id: ChatId, messag
|
||||
}
|
||||
|
||||
async fn send_archive_link(
|
||||
bot: CacheMe<Throttle<Bot>>,
|
||||
message: Box<Message>,
|
||||
task: Task,
|
||||
bot: &CacheMe<Throttle<Bot>>,
|
||||
chat_id: ChatId,
|
||||
message_id: MessageId,
|
||||
task: &Task,
|
||||
) -> BotHandlerInternal {
|
||||
let link = format!(
|
||||
"{}/api/download/{}",
|
||||
config::CONFIG.public_batch_downloader_url.clone(),
|
||||
config::CONFIG.public_batch_downloader_url,
|
||||
task.id
|
||||
);
|
||||
|
||||
bot.edit_message_text(
|
||||
message.chat.id,
|
||||
message.id,
|
||||
chat_id,
|
||||
message_id,
|
||||
format!(
|
||||
"Файл не может быть загружен в чат! \n \
|
||||
Вы можете скачать его <a href=\"{link}\">по ссылке</a> (работает 3 часа)"
|
||||
@@ -352,7 +353,7 @@ async fn wait_archive(
|
||||
let task = loop {
|
||||
interval.tick().await;
|
||||
|
||||
let task = match get_task(task_id.clone()).await {
|
||||
let task = match get_task(&task_id).await {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
send_error_message(bot, message.chat.id, message.id).await;
|
||||
@@ -388,18 +389,18 @@ async fn wait_archive(
|
||||
let content_size = task.content_size.unwrap();
|
||||
|
||||
if content_size > 1024 * 1024 * 1024 {
|
||||
send_archive_link(bot.clone(), message.clone(), task.clone()).await?;
|
||||
send_archive_link(&bot, message.chat.id, message.id, &task).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let link = format!(
|
||||
"{}/api/download/{}",
|
||||
config::CONFIG.batch_downloader_url.clone(),
|
||||
config::CONFIG.batch_downloader_url,
|
||||
task.id
|
||||
);
|
||||
|
||||
let downloaded_data =
|
||||
match download_file_by_link(task.clone().result_filename.unwrap(), link).await {
|
||||
match download_file_by_link(&task.clone().result_filename.unwrap(), link).await {
|
||||
Ok(v) => match v {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
@@ -416,14 +417,14 @@ async fn wait_archive(
|
||||
|
||||
match _send_downloaded_file(
|
||||
&MaybeInaccessibleMessage::Regular(message.clone()),
|
||||
bot.clone(),
|
||||
&bot,
|
||||
downloaded_data,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
send_archive_link(bot.clone(), message.clone(), task).await?;
|
||||
send_archive_link(&bot, message.chat.id, message.id, &task).await?;
|
||||
log::error!("{err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use self::commands::HelpCommand;
|
||||
pub async fn help_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal {
|
||||
let name = message
|
||||
.from
|
||||
.map(|user| user.first_name.clone())
|
||||
.map(|user| user.first_name)
|
||||
.unwrap_or("пользователь".to_string());
|
||||
|
||||
match bot
|
||||
|
||||
@@ -14,6 +14,7 @@ use teloxide::{
|
||||
|
||||
use crate::bots::{
|
||||
approved_bot::{
|
||||
modules::utils::message_text::is_message_text_equals,
|
||||
services::{
|
||||
book_library::{
|
||||
formatters::{Format, FormatTitle},
|
||||
@@ -45,7 +46,7 @@ where
|
||||
let chat_id = cq.chat_id();
|
||||
let user_id = cq.from.id;
|
||||
let message_id = cq.message.as_ref().map(|message| message.id());
|
||||
let query = get_query(cq);
|
||||
let query = get_query(cq.clone());
|
||||
|
||||
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),
|
||||
@@ -93,29 +94,33 @@ where
|
||||
};
|
||||
|
||||
if page > items_page.pages {
|
||||
items_page =
|
||||
match items_getter(query.clone(), items_page.pages, allowed_langs.clone()).await {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
bot.send_message(chat_id, "Ошибка! Попробуйте позже :(")
|
||||
.send()
|
||||
.await?;
|
||||
items_page = match items_getter(query, items_page.pages, allowed_langs).await {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
bot.send_message(chat_id, "Ошибка! Попробуйте позже :(")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let formatted_page = items_page.format(page, 4096);
|
||||
if is_message_text_equals(cq.message, &formatted_page) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let keyboard = generic_get_pagination_keyboard(page, items_page.pages, search_data, true);
|
||||
|
||||
bot.edit_message_text(chat_id, message_id, formatted_page)
|
||||
match bot
|
||||
.edit_message_text(chat_id, message_id, formatted_page)
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn message_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal {
|
||||
|
||||
@@ -118,10 +118,10 @@ async fn settings_callback_handler(
|
||||
|
||||
if let Err(err) = create_or_update_user_settings(
|
||||
user.id,
|
||||
user.last_name.clone().unwrap_or("".to_string()),
|
||||
user.first_name.clone(),
|
||||
user.username.clone().unwrap_or("".to_string()),
|
||||
me.username.clone().unwrap(),
|
||||
&user.last_name.unwrap_or("".to_string()),
|
||||
&user.first_name,
|
||||
&user.username.unwrap_or("".to_string()),
|
||||
&me.username.clone().unwrap(),
|
||||
allowed_langs_set.clone().into_iter().collect(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -16,20 +16,20 @@ enum SupportCommand {
|
||||
|
||||
pub async fn support_command_handler(
|
||||
message: Message,
|
||||
bot: CacheMe<Throttle<Bot>>,
|
||||
bot: &CacheMe<Throttle<Bot>>,
|
||||
) -> BotHandlerInternal {
|
||||
let username = match message.clone().from {
|
||||
Some(user) => match user.is_bot {
|
||||
true => match message.reply_to_message() {
|
||||
Some(v) => match &v.from {
|
||||
Some(v) => v.first_name.clone(),
|
||||
None => "пользователь".to_string(),
|
||||
Some(v) => &v.first_name,
|
||||
None => "пользователь",
|
||||
},
|
||||
None => "пользователь".to_string(),
|
||||
None => "пользователь",
|
||||
},
|
||||
false => user.first_name,
|
||||
false => &user.first_name.clone(),
|
||||
},
|
||||
None => "пользователь".to_string(),
|
||||
None => "пользователь",
|
||||
};
|
||||
|
||||
let message_text = format!(
|
||||
@@ -60,5 +60,7 @@ pub async fn support_command_handler(
|
||||
pub fn get_support_handler() -> crate::bots::BotHandler {
|
||||
Update::filter_message()
|
||||
.filter_command::<SupportCommand>()
|
||||
.endpoint(support_command_handler)
|
||||
.endpoint(|message: Message, bot: CacheMe<Throttle<Bot>>| async move {
|
||||
support_command_handler(message, &bot).await
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ pub mod commands;
|
||||
use chrono::{prelude::*, Duration};
|
||||
|
||||
use crate::bots::{
|
||||
approved_bot::{services::book_library::get_uploaded_books, tools::filter_callback_query},
|
||||
approved_bot::{
|
||||
modules::utils::message_text::is_message_text_equals,
|
||||
services::book_library::get_uploaded_books, tools::filter_callback_query,
|
||||
},
|
||||
BotHandlerInternal,
|
||||
};
|
||||
|
||||
@@ -78,7 +81,7 @@ async fn update_log_pagination_handler(
|
||||
bot: CacheMe<Throttle<Bot>>,
|
||||
update_callback_data: UpdateLogCallbackData,
|
||||
) -> BotHandlerInternal {
|
||||
let message = match cq.message {
|
||||
let message = match cq.message.clone() {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
bot.send_message(cq.from.id, "Ошибка! Попробуйте заново(")
|
||||
@@ -138,14 +141,20 @@ async fn update_log_pagination_handler(
|
||||
let formatted_page = items_page.format(page, 4096);
|
||||
|
||||
let message_text = format!("{header}{formatted_page}");
|
||||
if is_message_text_equals(cq.message, &message_text) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let keyboard = generic_get_pagination_keyboard(page, total_pages, update_callback_data, true);
|
||||
bot.edit_message_text(message.chat().id, message.id(), message_text)
|
||||
match bot
|
||||
.edit_message_text(message.chat().id, message.id(), message_text)
|
||||
.reply_markup(keyboard)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_update_log_handler() -> crate::bots::BotHandler {
|
||||
|
||||
18
src/bots/approved_bot/modules/utils/message_text.rs
Normal file
18
src/bots/approved_bot/modules/utils/message_text.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use teloxide::types::*;
|
||||
|
||||
pub fn is_message_text_equals(message: Option<MaybeInaccessibleMessage>, text: &str) -> bool {
|
||||
let message = match message {
|
||||
Some(v) => v,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
let message = match message {
|
||||
MaybeInaccessibleMessage::Inaccessible(_) => return false,
|
||||
MaybeInaccessibleMessage::Regular(v) => v,
|
||||
};
|
||||
|
||||
match message.text() {
|
||||
Some(msg_text) => text == msg_text,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod errors;
|
||||
pub mod filter_command;
|
||||
pub mod message_text;
|
||||
pub mod pagination;
|
||||
pub mod split_text;
|
||||
|
||||
@@ -56,7 +56,7 @@ pub async fn create_task(data: CreateTaskData) -> anyhow::Result<Task> {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_task(task_id: String) -> anyhow::Result<Task> {
|
||||
pub async fn get_task(task_id: &str) -> anyhow::Result<Task> {
|
||||
Ok(CLIENT
|
||||
.get(format!(
|
||||
"{}/api/check_archive/{task_id}",
|
||||
|
||||
@@ -91,7 +91,7 @@ pub async fn download_file(
|
||||
}
|
||||
|
||||
pub async fn download_file_by_link(
|
||||
filename: String,
|
||||
filename: &str,
|
||||
link: String,
|
||||
) -> anyhow::Result<Option<DownloadFile>> {
|
||||
let response = CLIENT.get(link).send().await?;
|
||||
@@ -102,7 +102,7 @@ pub async fn download_file_by_link(
|
||||
|
||||
Ok(Some(DownloadFile {
|
||||
response,
|
||||
filename,
|
||||
filename: filename.to_string(),
|
||||
caption: "".to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ impl FormatInline for BookTranslator {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_authors(authors: Vec<BookAuthor>, count: usize) -> String {
|
||||
fn format_authors(authors: &[BookAuthor], count: usize) -> String {
|
||||
if count == 0 {
|
||||
return "".to_string();
|
||||
}
|
||||
@@ -139,7 +139,7 @@ fn format_authors(authors: Vec<BookAuthor>, count: usize) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_translators(translators: Vec<BookTranslator>, count: usize) -> String {
|
||||
fn format_translators(translators: &[BookTranslator], count: usize) -> String {
|
||||
if count == 0 {
|
||||
return "".to_string();
|
||||
}
|
||||
@@ -163,7 +163,7 @@ fn format_translators(translators: Vec<BookTranslator>, count: usize) -> String
|
||||
}
|
||||
}
|
||||
|
||||
fn format_sequences(sequences: Vec<Sequence>, count: usize) -> String {
|
||||
fn format_sequences(sequences: &[Sequence], count: usize) -> String {
|
||||
if count == 0 {
|
||||
return "".to_string();
|
||||
}
|
||||
@@ -187,7 +187,7 @@ fn format_sequences(sequences: Vec<Sequence>, count: usize) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_genres(genres: Vec<BookGenre>, count: usize) -> String {
|
||||
fn format_genres(genres: &[BookGenre], count: usize) -> String {
|
||||
if count == 0 {
|
||||
return "".to_string();
|
||||
}
|
||||
@@ -395,117 +395,231 @@ impl FormatVectorsResult {
|
||||
}
|
||||
}
|
||||
|
||||
impl Book {
|
||||
fn format_vectors(&self, max_size: usize) -> FormatVectorsResult {
|
||||
let mut counts = FormatVectorsCounts {
|
||||
authors: self.authors.len(),
|
||||
translators: self.translators.len(),
|
||||
sequences: self.sequences.len(),
|
||||
genres: self.genres.len(),
|
||||
};
|
||||
fn format_vectors(
|
||||
authors: &[BookAuthor],
|
||||
translators: &[BookTranslator],
|
||||
sequences: &[Sequence],
|
||||
genres: &[BookGenre],
|
||||
max_size: usize,
|
||||
) -> FormatVectorsResult {
|
||||
let mut counts = FormatVectorsCounts {
|
||||
authors: authors.len(),
|
||||
translators: translators.len(),
|
||||
sequences: sequences.len(),
|
||||
genres: genres.len(),
|
||||
};
|
||||
|
||||
let mut result = FormatVectorsResult {
|
||||
authors: format_authors(self.authors.clone(), counts.authors),
|
||||
translators: format_translators(self.translators.clone(), counts.translators),
|
||||
sequences: format_sequences(self.sequences.clone(), counts.sequences),
|
||||
genres: format_genres(self.genres.clone(), counts.genres),
|
||||
let mut result = FormatVectorsResult {
|
||||
authors: format_authors(authors, counts.authors),
|
||||
translators: format_translators(translators, counts.translators),
|
||||
sequences: format_sequences(sequences, counts.sequences),
|
||||
genres: format_genres(genres, counts.genres),
|
||||
max_result_size: 0,
|
||||
};
|
||||
|
||||
let max_result_size = result.len();
|
||||
|
||||
while result.len() > max_size && counts.can_sub() {
|
||||
counts = counts.sub();
|
||||
|
||||
result = FormatVectorsResult {
|
||||
authors: format_authors(authors, counts.authors),
|
||||
translators: format_translators(translators, counts.translators),
|
||||
sequences: format_sequences(sequences, counts.sequences),
|
||||
genres: format_genres(genres, counts.genres),
|
||||
max_result_size: 0,
|
||||
};
|
||||
}
|
||||
|
||||
let max_result_size = result.len();
|
||||
result.with_max_result_size(max_result_size)
|
||||
}
|
||||
|
||||
while result.len() > max_size && counts.can_sub() {
|
||||
counts = counts.sub();
|
||||
struct FormatData<'a> {
|
||||
pub id: u32,
|
||||
pub title: &'a str,
|
||||
pub lang: &'a str,
|
||||
pub annotation_exists: bool,
|
||||
pub authors: &'a [BookAuthor],
|
||||
pub translators: &'a [BookTranslator],
|
||||
pub sequences: &'a [Sequence],
|
||||
pub genres: &'a [BookGenre],
|
||||
pub year: i32,
|
||||
pub pages: Option<u32>,
|
||||
pub position: Option<i32>,
|
||||
}
|
||||
|
||||
result = FormatVectorsResult {
|
||||
authors: format_authors(self.authors.clone(), counts.authors),
|
||||
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,
|
||||
};
|
||||
fn format_common(data: FormatData, max_size: usize) -> FormatResult {
|
||||
let FormatData {
|
||||
id,
|
||||
title,
|
||||
lang,
|
||||
annotation_exists,
|
||||
authors,
|
||||
translators,
|
||||
sequences,
|
||||
genres,
|
||||
year,
|
||||
pages,
|
||||
position,
|
||||
} = data;
|
||||
|
||||
let book_title = {
|
||||
let year_part = match year {
|
||||
0 => "".to_string(),
|
||||
v => format!(" | {v}г."),
|
||||
};
|
||||
|
||||
let pages_count = match pages {
|
||||
Some(1) | None => "".to_string(),
|
||||
Some(v) => format!(" | {v}с."),
|
||||
};
|
||||
|
||||
let position_prefix = match position {
|
||||
Some(0) | None => "".to_string(),
|
||||
Some(v) => format!("{v} | "),
|
||||
};
|
||||
|
||||
format!("{position_prefix}📖 {title} | {lang}{year_part}{pages_count}\n")
|
||||
};
|
||||
|
||||
let annotations = match annotation_exists {
|
||||
true => {
|
||||
format!("📝 Аннотация: /b_an_{id}\n")
|
||||
}
|
||||
false => "".to_string(),
|
||||
};
|
||||
|
||||
result.with_max_result_size(max_result_size)
|
||||
let download_command = (StartDownloadCommand { id }).to_string();
|
||||
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,
|
||||
} = format_vectors(
|
||||
authors,
|
||||
translators,
|
||||
sequences,
|
||||
genres,
|
||||
max_size - required_data_len,
|
||||
);
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Book {
|
||||
fn format(&self, max_size: usize) -> FormatResult {
|
||||
let book_title = {
|
||||
let Book { title, lang, .. } = self;
|
||||
|
||||
let year_part = match self.year {
|
||||
0 => "".to_string(),
|
||||
v => format!(" | {v}г."),
|
||||
};
|
||||
|
||||
let pages_count = match self.pages {
|
||||
Some(1) | None => "".to_string(),
|
||||
Some(v) => format!(" | {v}с."),
|
||||
};
|
||||
|
||||
let position_prefix = match self.position {
|
||||
Some(0) | None => "".to_string(),
|
||||
Some(v) => format!("{v} | "),
|
||||
};
|
||||
|
||||
format!("{position_prefix}📖 {title} | {lang}{year_part}{pages_count}\n")
|
||||
};
|
||||
|
||||
let annotations = match self.annotation_exists {
|
||||
true => {
|
||||
let Book { id, .. } = self;
|
||||
format!("📝 Аннотация: /b_an_{id}\n")
|
||||
}
|
||||
false => "".to_string(),
|
||||
};
|
||||
|
||||
let download_command = (StartDownloadCommand { id: self.id }).to_string();
|
||||
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 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,
|
||||
}
|
||||
format_common(
|
||||
FormatData {
|
||||
id: self.id,
|
||||
title: &self.title,
|
||||
lang: &self.lang,
|
||||
annotation_exists: self.annotation_exists,
|
||||
authors: &self.authors,
|
||||
translators: &self.translators,
|
||||
sequences: &self.sequences,
|
||||
genres: &self.genres,
|
||||
year: self.year,
|
||||
pages: self.pages,
|
||||
position: self.position,
|
||||
},
|
||||
max_size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for SearchBook {
|
||||
fn format(&self, max_size: usize) -> FormatResult {
|
||||
Into::<Book>::into(self.clone()).format(max_size)
|
||||
format_common(
|
||||
FormatData {
|
||||
id: self.id,
|
||||
title: &self.title,
|
||||
lang: &self.lang,
|
||||
annotation_exists: self.annotation_exists,
|
||||
authors: &self.authors,
|
||||
translators: &self.translators,
|
||||
sequences: &self.sequences,
|
||||
genres: &[],
|
||||
year: self.year,
|
||||
pages: None,
|
||||
position: None,
|
||||
},
|
||||
max_size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for AuthorBook {
|
||||
fn format(&self, max_size: usize) -> FormatResult {
|
||||
Into::<Book>::into(self.clone()).format(max_size)
|
||||
format_common(
|
||||
FormatData {
|
||||
id: self.id,
|
||||
title: &self.title,
|
||||
lang: &self.lang,
|
||||
annotation_exists: self.annotation_exists,
|
||||
authors: &[],
|
||||
translators: &self.translators,
|
||||
sequences: &self.sequences,
|
||||
genres: &[],
|
||||
year: self.year,
|
||||
pages: None,
|
||||
position: None,
|
||||
},
|
||||
max_size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for TranslatorBook {
|
||||
fn format(&self, max_size: usize) -> FormatResult {
|
||||
Into::<Book>::into(self.clone()).format(max_size)
|
||||
format_common(
|
||||
FormatData {
|
||||
id: self.id,
|
||||
title: &self.title,
|
||||
lang: &self.lang,
|
||||
annotation_exists: self.annotation_exists,
|
||||
authors: &self.authors,
|
||||
translators: &[],
|
||||
sequences: &self.sequences,
|
||||
genres: &[],
|
||||
year: self.year,
|
||||
pages: None,
|
||||
position: None,
|
||||
},
|
||||
max_size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for SequenceBook {
|
||||
fn format(&self, max_size: usize) -> FormatResult {
|
||||
Into::<Book>::into(self.clone()).format(max_size)
|
||||
format_common(
|
||||
FormatData {
|
||||
id: self.id,
|
||||
title: &self.title,
|
||||
lang: &self.lang,
|
||||
annotation_exists: self.annotation_exists,
|
||||
authors: &self.authors,
|
||||
translators: &self.translators,
|
||||
sequences: &[],
|
||||
genres: &[],
|
||||
year: self.year,
|
||||
pages: None,
|
||||
position: Some(self.position),
|
||||
},
|
||||
max_size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ use self::types::Empty;
|
||||
pub static CLIENT: Lazy<reqwest::Client> = Lazy::new(reqwest::Client::new);
|
||||
|
||||
fn get_allowed_langs_params(
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
allowed_langs: &SmallVec<[SmartString; 3]>,
|
||||
) -> Vec<(&'static str, SmartString)> {
|
||||
allowed_langs
|
||||
.into_iter()
|
||||
.map(|lang| ("allowed_langs", lang))
|
||||
.map(|lang| ("allowed_langs", lang.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ pub async fn get_random_book_by_genre(
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
genre: Option<u32>,
|
||||
) -> anyhow::Result<types::Book> {
|
||||
let mut params = get_allowed_langs_params(allowed_langs);
|
||||
let mut params = get_allowed_langs_params(&allowed_langs);
|
||||
|
||||
if let Some(v) = genre {
|
||||
params.push(("genre", v.to_string().into()));
|
||||
@@ -70,7 +70,7 @@ pub async fn get_random_book(
|
||||
pub async fn get_random_author(
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
) -> anyhow::Result<types::Author> {
|
||||
let params = get_allowed_langs_params(allowed_langs);
|
||||
let params = get_allowed_langs_params(&allowed_langs);
|
||||
|
||||
_make_request("/api/v1/authors/random", params).await
|
||||
}
|
||||
@@ -78,7 +78,7 @@ pub async fn get_random_author(
|
||||
pub async fn get_random_sequence(
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
) -> anyhow::Result<types::Sequence> {
|
||||
let params = get_allowed_langs_params(allowed_langs);
|
||||
let params = get_allowed_langs_params(&allowed_langs);
|
||||
|
||||
_make_request("/api/v1/sequences/random", params).await
|
||||
}
|
||||
@@ -100,7 +100,7 @@ pub async fn search_book(
|
||||
page: u32,
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
) -> anyhow::Result<types::Page<types::SearchBook, Empty>> {
|
||||
let mut params = get_allowed_langs_params(allowed_langs);
|
||||
let mut params = get_allowed_langs_params(&allowed_langs);
|
||||
|
||||
params.push(("page", page.to_string().into()));
|
||||
params.push(("size", PAGE_SIZE.to_string().into()));
|
||||
@@ -113,7 +113,7 @@ pub async fn search_author(
|
||||
page: u32,
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
) -> anyhow::Result<types::Page<types::Author, Empty>> {
|
||||
let mut params = get_allowed_langs_params(allowed_langs);
|
||||
let mut params = get_allowed_langs_params(&allowed_langs);
|
||||
|
||||
params.push(("page", page.to_string().into()));
|
||||
params.push(("size", PAGE_SIZE.to_string().into()));
|
||||
@@ -126,7 +126,7 @@ pub async fn search_sequence(
|
||||
page: u32,
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
) -> anyhow::Result<types::Page<types::Sequence, Empty>> {
|
||||
let mut params = get_allowed_langs_params(allowed_langs);
|
||||
let mut params = get_allowed_langs_params(&allowed_langs);
|
||||
|
||||
params.push(("page", page.to_string().into()));
|
||||
params.push(("size", PAGE_SIZE.to_string().into()));
|
||||
@@ -139,7 +139,7 @@ pub async fn search_translator(
|
||||
page: u32,
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
) -> anyhow::Result<types::Page<types::Translator, Empty>> {
|
||||
let mut params = get_allowed_langs_params(allowed_langs);
|
||||
let mut params = get_allowed_langs_params(&allowed_langs);
|
||||
|
||||
params.push(("page", page.to_string().into()));
|
||||
params.push(("size", PAGE_SIZE.to_string().into()));
|
||||
@@ -164,7 +164,7 @@ pub async fn get_author_books(
|
||||
page: u32,
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
) -> anyhow::Result<types::Page<types::AuthorBook, types::BookAuthor>> {
|
||||
let mut params = get_allowed_langs_params(allowed_langs);
|
||||
let mut params = get_allowed_langs_params(&allowed_langs);
|
||||
|
||||
params.push(("page", page.to_string().into()));
|
||||
params.push(("size", PAGE_SIZE.to_string().into()));
|
||||
@@ -177,7 +177,7 @@ pub async fn get_translator_books(
|
||||
page: u32,
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
) -> anyhow::Result<types::Page<types::TranslatorBook, types::BookTranslator>> {
|
||||
let mut params = get_allowed_langs_params(allowed_langs);
|
||||
let mut params = get_allowed_langs_params(&allowed_langs);
|
||||
|
||||
params.push(("page", page.to_string().into()));
|
||||
params.push(("size", PAGE_SIZE.to_string().into()));
|
||||
@@ -190,7 +190,7 @@ pub async fn get_sequence_books(
|
||||
page: u32,
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
) -> anyhow::Result<types::Page<types::SequenceBook, types::Sequence>> {
|
||||
let mut params = get_allowed_langs_params(allowed_langs);
|
||||
let mut params = get_allowed_langs_params(&allowed_langs);
|
||||
|
||||
params.push(("page", page.to_string().into()));
|
||||
params.push(("size", PAGE_SIZE.to_string().into()));
|
||||
@@ -216,7 +216,7 @@ pub async fn get_uploaded_books(
|
||||
|
||||
pub async fn get_author_books_available_types(
|
||||
id: u32,
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
allowed_langs: &SmallVec<[SmartString; 3]>,
|
||||
) -> anyhow::Result<Vec<String>> {
|
||||
let params = get_allowed_langs_params(allowed_langs);
|
||||
|
||||
@@ -229,7 +229,7 @@ pub async fn get_author_books_available_types(
|
||||
|
||||
pub async fn get_translator_books_available_types(
|
||||
id: u32,
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
allowed_langs: &SmallVec<[SmartString; 3]>,
|
||||
) -> anyhow::Result<Vec<String>> {
|
||||
let params = get_allowed_langs_params(allowed_langs);
|
||||
|
||||
@@ -242,7 +242,7 @@ pub async fn get_translator_books_available_types(
|
||||
|
||||
pub async fn get_sequence_books_available_types(
|
||||
id: u32,
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
allowed_langs: &SmallVec<[SmartString; 3]>,
|
||||
) -> anyhow::Result<Vec<String>> {
|
||||
let params = get_allowed_langs_params(allowed_langs);
|
||||
|
||||
|
||||
@@ -32,12 +32,6 @@ impl BookGenre {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct Source {
|
||||
// id: u32,
|
||||
// name: String
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct Author {
|
||||
pub id: u32,
|
||||
@@ -149,6 +143,8 @@ where
|
||||
.map(|item| item_size - item.current_size)
|
||||
.sum();
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
self.items
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -156,17 +152,17 @@ where
|
||||
let already_formated_result = &format_result[index];
|
||||
|
||||
if already_formated_result.current_size == already_formated_result.max_size {
|
||||
already_formated_result.result.clone()
|
||||
Cow::Borrowed(already_formated_result.result.as_str())
|
||||
} else {
|
||||
let new_item_size = item_size + free_symbols;
|
||||
let new_formated_result = item.format(new_item_size);
|
||||
|
||||
free_symbols = new_item_size - new_formated_result.current_size;
|
||||
|
||||
new_formated_result.result
|
||||
Cow::Owned(new_formated_result.result)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.collect::<Vec<Cow<str>>>()
|
||||
.join(separator)
|
||||
}
|
||||
}
|
||||
@@ -192,17 +188,12 @@ pub struct Book {
|
||||
pub id: u32,
|
||||
pub title: String,
|
||||
pub lang: String,
|
||||
// file_type: String,
|
||||
pub available_types: SmallVec<[String; 4]>,
|
||||
// uploaded: String,
|
||||
pub annotation_exists: bool,
|
||||
pub authors: Vec<BookAuthor>,
|
||||
pub translators: Vec<BookTranslator>,
|
||||
pub sequences: Vec<Sequence>,
|
||||
pub genres: Vec<BookGenre>,
|
||||
// source: Source,
|
||||
// remote_id: u32,
|
||||
// id_deleted: bool,
|
||||
pub year: i32,
|
||||
pub pages: Option<u32>,
|
||||
pub position: Option<i32>,
|
||||
@@ -213,9 +204,6 @@ pub struct SearchBook {
|
||||
pub id: u32,
|
||||
pub title: String,
|
||||
pub lang: String,
|
||||
// file_type: String,
|
||||
pub available_types: SmallVec<[String; 4]>,
|
||||
// uploaded: String,
|
||||
pub annotation_exists: bool,
|
||||
pub authors: Vec<BookAuthor>,
|
||||
pub translators: Vec<BookTranslator>,
|
||||
@@ -223,121 +211,36 @@ pub struct SearchBook {
|
||||
pub year: i32,
|
||||
}
|
||||
|
||||
impl From<SearchBook> for Book {
|
||||
fn from(value: SearchBook) -> Self {
|
||||
Book {
|
||||
id: value.id,
|
||||
title: value.title,
|
||||
lang: value.lang,
|
||||
available_types: value.available_types,
|
||||
annotation_exists: value.annotation_exists,
|
||||
authors: value.authors,
|
||||
translators: value.translators,
|
||||
sequences: value.sequences,
|
||||
genres: vec![],
|
||||
pages: None,
|
||||
year: value.year,
|
||||
position: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct AuthorBook {
|
||||
pub id: u32,
|
||||
pub title: String,
|
||||
pub lang: String,
|
||||
// file_type: String,
|
||||
pub available_types: SmallVec<[String; 4]>,
|
||||
// uploaded: String,
|
||||
pub annotation_exists: bool,
|
||||
pub translators: Vec<BookTranslator>,
|
||||
pub sequences: Vec<Sequence>,
|
||||
pub year: i32,
|
||||
}
|
||||
|
||||
impl From<AuthorBook> for Book {
|
||||
fn from(value: AuthorBook) -> Self {
|
||||
Book {
|
||||
id: value.id,
|
||||
title: value.title,
|
||||
lang: value.lang,
|
||||
available_types: value.available_types,
|
||||
annotation_exists: value.annotation_exists,
|
||||
authors: vec![],
|
||||
translators: value.translators,
|
||||
sequences: value.sequences,
|
||||
genres: vec![],
|
||||
pages: None,
|
||||
year: value.year,
|
||||
position: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct TranslatorBook {
|
||||
pub id: u32,
|
||||
pub title: String,
|
||||
pub lang: String,
|
||||
// file_type: String,
|
||||
pub available_types: SmallVec<[String; 4]>,
|
||||
// uploaded: String,
|
||||
pub annotation_exists: bool,
|
||||
pub authors: Vec<BookAuthor>,
|
||||
pub sequences: Vec<Sequence>,
|
||||
pub year: i32,
|
||||
}
|
||||
|
||||
impl From<TranslatorBook> for Book {
|
||||
fn from(value: TranslatorBook) -> Self {
|
||||
Book {
|
||||
id: value.id,
|
||||
title: value.title,
|
||||
lang: value.lang,
|
||||
available_types: value.available_types,
|
||||
annotation_exists: value.annotation_exists,
|
||||
authors: value.authors,
|
||||
translators: vec![],
|
||||
sequences: value.sequences,
|
||||
genres: vec![],
|
||||
pages: None,
|
||||
year: value.year,
|
||||
position: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct SequenceBook {
|
||||
pub id: u32,
|
||||
pub title: String,
|
||||
pub lang: String,
|
||||
// file_type: String,
|
||||
pub available_types: SmallVec<[String; 4]>,
|
||||
// uploaded: String,
|
||||
pub authors: Vec<BookAuthor>,
|
||||
pub translators: Vec<BookTranslator>,
|
||||
pub annotation_exists: bool,
|
||||
pub year: i32,
|
||||
pub position: i32,
|
||||
}
|
||||
|
||||
impl From<SequenceBook> for Book {
|
||||
fn from(value: SequenceBook) -> Self {
|
||||
Book {
|
||||
id: value.id,
|
||||
title: value.title,
|
||||
lang: value.lang,
|
||||
available_types: value.available_types,
|
||||
annotation_exists: value.annotation_exists,
|
||||
authors: value.authors,
|
||||
translators: value.translators,
|
||||
sequences: vec![],
|
||||
genres: vec![],
|
||||
pages: None,
|
||||
year: value.year,
|
||||
position: Some(value.position),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ use crate::{
|
||||
use super::user_settings::{is_need_donate_notifications, mark_donate_notification_sent};
|
||||
|
||||
pub async fn send_donation_notification(
|
||||
bot: CacheMe<Throttle<Bot>>,
|
||||
message: MaybeInaccessibleMessage,
|
||||
bot: &CacheMe<Throttle<Bot>>,
|
||||
message: &MaybeInaccessibleMessage,
|
||||
) -> BotHandlerInternal {
|
||||
if CHAT_DONATION_NOTIFICATIONS_CACHE
|
||||
.get(&message.chat().id)
|
||||
@@ -31,7 +31,7 @@ pub async fn send_donation_notification(
|
||||
mark_donate_notification_sent(message.chat().id).await?;
|
||||
|
||||
if let MaybeInaccessibleMessage::Regular(message) = message {
|
||||
support_command_handler(*message, bot).await?;
|
||||
support_command_handler(*message.clone(), bot).await?;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -74,10 +74,10 @@ pub async fn get_user_or_default_lang_codes(user_id: UserId) -> SmallVec<[SmartS
|
||||
|
||||
pub async fn create_or_update_user_settings(
|
||||
user_id: UserId,
|
||||
last_name: String,
|
||||
first_name: String,
|
||||
username: String,
|
||||
source: String,
|
||||
last_name: &str,
|
||||
first_name: &str,
|
||||
username: &str,
|
||||
source: &str,
|
||||
allowed_langs: SmallVec<[SmartString; 3]>,
|
||||
) -> anyhow::Result<UserSettings> {
|
||||
USER_LANGS_CACHE.invalidate(&user_id).await;
|
||||
|
||||
@@ -19,9 +19,10 @@ pub async fn message_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> a
|
||||
|
||||
let message_text = match result {
|
||||
register::RegisterStatus::Success { ref username } => format_registered_message(username),
|
||||
register::RegisterStatus::RegisterFail => strings::ALREADY_REGISTERED.to_string(),
|
||||
register::RegisterStatus::RegisterFail => strings::MAY_BE_ALREADY_REGISTERED.to_string(),
|
||||
register::RegisterStatus::LimitExtended => strings::LIMIT_EXTENDED_MESSAGE.to_string(),
|
||||
register::RegisterStatus::WrongToken => strings::ERROR_MESSAGE.to_string(),
|
||||
register::RegisterStatus::AlreadyExists => strings::ALREADY_EXISTS_MESSAGE.to_string(),
|
||||
};
|
||||
|
||||
bot.send_message(message.chat.id, message_text)
|
||||
|
||||
@@ -12,12 +12,14 @@ pub enum RegisterStatus {
|
||||
WrongToken,
|
||||
RegisterFail,
|
||||
LimitExtended,
|
||||
AlreadyExists,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RegisterRequestStatus {
|
||||
Success,
|
||||
LimitExtended,
|
||||
AlreadyExists,
|
||||
UnknownError,
|
||||
}
|
||||
|
||||
@@ -42,9 +44,9 @@ async fn make_register_request(
|
||||
});
|
||||
|
||||
let result = reqwest::Client::new()
|
||||
.post(config::CONFIG.manager_url.clone())
|
||||
.post(&config::CONFIG.manager_url)
|
||||
.body(body.to_string())
|
||||
.header("Authorization", config::CONFIG.manager_api_key.clone())
|
||||
.header("Authorization", &config::CONFIG.manager_api_key)
|
||||
.header("Content-Type", "application/json")
|
||||
.send()
|
||||
.await?;
|
||||
@@ -52,6 +54,7 @@ async fn make_register_request(
|
||||
Ok(match result.status().as_u16() {
|
||||
200 => RegisterRequestStatus::Success,
|
||||
402 => RegisterRequestStatus::LimitExtended,
|
||||
409 => RegisterRequestStatus::AlreadyExists,
|
||||
_ => RegisterRequestStatus::UnknownError,
|
||||
})
|
||||
}
|
||||
@@ -81,5 +84,6 @@ pub async fn register(user_id: UserId, message_text: &str) -> RegisterStatus {
|
||||
},
|
||||
RegisterRequestStatus::LimitExtended => RegisterStatus::LimitExtended,
|
||||
RegisterRequestStatus::UnknownError => RegisterStatus::RegisterFail,
|
||||
RegisterRequestStatus::AlreadyExists => RegisterStatus::AlreadyExists,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ pub fn format_registered_message(username: &str) -> String {
|
||||
format!("@{username} зарегистрирован и через несколько минут будет подключен!")
|
||||
}
|
||||
|
||||
pub const ALREADY_REGISTERED: &str = "Ошибка! Возможно бот уже зарегистрирован!";
|
||||
pub const MAY_BE_ALREADY_REGISTERED: &str = "Ошибка! Возможно бот уже зарегистрирован!";
|
||||
|
||||
pub const ERROR_MESSAGE: &str = "Ошибка! Что-то не так с ботом!";
|
||||
|
||||
pub const LIMIT_EXTENDED_MESSAGE: &str = "Вы достигли максимального количества ботов!";
|
||||
|
||||
pub const ALREADY_EXISTS_MESSAGE: &str = "Ошибка! Бот с таким токеном уже зарегистрирован!";
|
||||
|
||||
85
src/bots_manager/custom_error_handler.rs
Normal file
85
src/bots_manager/custom_error_handler.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use tracing::log;
|
||||
|
||||
pub struct CustomErrorHandler {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
impl CustomErrorHandler {
|
||||
pub fn with_custom_text<T>(text: T) -> Arc<Self>
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Arc::new(Self { text: text.into() })
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> teloxide::error_handlers::ErrorHandler<E> for CustomErrorHandler
|
||||
where
|
||||
E: std::fmt::Debug + Send + 'static,
|
||||
{
|
||||
fn handle_error(
|
||||
self: Arc<Self>,
|
||||
error: E,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> {
|
||||
Box::pin(async move {
|
||||
let error_string = format!("{:?}", error);
|
||||
|
||||
if error_string.contains("Bad Request: message to be replied not found") {
|
||||
log::debug!("Ignoring Telegram reply error: {:?}", error);
|
||||
return;
|
||||
}
|
||||
|
||||
let backtrace = std::backtrace::Backtrace::force_capture();
|
||||
|
||||
let error_chain = if let Some(std_error) =
|
||||
(&error as &dyn std::any::Any).downcast_ref::<Box<dyn std::error::Error>>()
|
||||
{
|
||||
let mut chain = Vec::new();
|
||||
let mut source = std_error.source();
|
||||
while let Some(err) = source {
|
||||
chain.push(format!(" Caused by: {}", err));
|
||||
source = err.source();
|
||||
}
|
||||
if chain.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("\nError chain:\n{}", chain.join("\n"))
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let backtrace_info = match backtrace.status() {
|
||||
std::backtrace::BacktraceStatus::Captured => {
|
||||
format!("\nBacktrace:\n{}", backtrace)
|
||||
}
|
||||
std::backtrace::BacktraceStatus::Disabled => {
|
||||
"\nBacktrace: disabled (compile with debug info for stack traces)".to_string()
|
||||
}
|
||||
std::backtrace::BacktraceStatus::Unsupported => {
|
||||
"\nBacktrace: unsupported on this platform".to_string()
|
||||
}
|
||||
_ => String::new(),
|
||||
};
|
||||
|
||||
log::error!(
|
||||
"{}: {:?}{}{}",
|
||||
self.text,
|
||||
error,
|
||||
error_chain,
|
||||
backtrace_info
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CustomErrorHandler {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
text: "An error from the update listener".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::custom_error_handler::CustomErrorHandler;
|
||||
use teloxide::adaptors::throttle::Limits;
|
||||
use teloxide::dispatching::Dispatcher;
|
||||
use teloxide::error_handlers::LoggingErrorHandler;
|
||||
|
||||
use teloxide::requests::{Request, Requester, RequesterExt};
|
||||
use teloxide::stop::StopToken;
|
||||
use teloxide::stop::{mk_stop_token, StopFlag};
|
||||
@@ -108,7 +109,7 @@ pub async fn start_bot(bot_data: &BotData) {
|
||||
dispatcher
|
||||
.dispatch_with_listener(
|
||||
listener,
|
||||
LoggingErrorHandler::with_custom_text("An error from the update listener"),
|
||||
CustomErrorHandler::with_custom_text("An error from the update listener"),
|
||||
)
|
||||
.await;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod axum_server;
|
||||
pub mod bot_manager_client;
|
||||
pub mod closable_sender;
|
||||
pub mod custom_error_handler;
|
||||
pub mod internal;
|
||||
pub mod utils;
|
||||
|
||||
@@ -93,13 +94,11 @@ impl BotsManager {
|
||||
|
||||
let bot_data: BotData = bot_data.clone();
|
||||
|
||||
BOTS_DATA
|
||||
.insert(bot_data.token.clone(), bot_data.clone())
|
||||
.await;
|
||||
BOTS_DATA.insert(bot_data.token.clone(), bot_data).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_unininted(bots_data: &[BotData]) {
|
||||
async fn check_uninited(bots_data: &[BotData]) {
|
||||
let semaphore = Arc::new(Semaphore::const_new(5));
|
||||
let mut set_webhook_tasks = JoinSet::new();
|
||||
|
||||
@@ -110,9 +109,9 @@ impl BotsManager {
|
||||
|
||||
let bot_data: BotData = bot_data.clone();
|
||||
|
||||
let semphore = semaphore.clone();
|
||||
let semaphore = semaphore.clone();
|
||||
set_webhook_tasks.spawn(async move {
|
||||
let _permit = semphore.acquire().await.unwrap();
|
||||
let _permit = semaphore.acquire().await.unwrap();
|
||||
|
||||
let webhook_status = set_webhook(&bot_data).await;
|
||||
|
||||
@@ -145,7 +144,7 @@ impl BotsManager {
|
||||
let _ = BotsManager::check_bots_data(&bots_data).await;
|
||||
|
||||
if !only_bot_data {
|
||||
let _ = BotsManager::check_unininted(&bots_data).await;
|
||||
let _ = BotsManager::check_uninited(&bots_data).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +175,7 @@ impl BotsManager {
|
||||
|
||||
match result {
|
||||
Ok(webhook_info) => {
|
||||
if webhook_info.pending_update_count != 0 {
|
||||
if webhook_info.pending_update_count == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -187,7 +186,9 @@ impl BotsManager {
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
if err.to_string().contains("Api(InvalidToken)") {
|
||||
let error_message = err.to_string();
|
||||
|
||||
if error_message.contains("Invalid bot token") {
|
||||
BOTS_DATA.invalidate(token.as_str()).await;
|
||||
if let Err(d_err) = delete_bot(bot_data.id).await {
|
||||
log::error!("Error deleting bot {}: {:?}", bot_data.id, d_err);
|
||||
@@ -195,7 +196,7 @@ impl BotsManager {
|
||||
continue;
|
||||
}
|
||||
|
||||
log::error!("Error getting webhook info: {err:?}");
|
||||
log::error!("Error getting webhook info: {error_message}");
|
||||
|
||||
WEBHOOK_CHECK_ERRORS_COUNT
|
||||
.insert(bot_data.id, error_count + 1)
|
||||
@@ -224,11 +225,11 @@ impl BotsManager {
|
||||
BotsManager::check(false).await;
|
||||
}
|
||||
|
||||
if tick_number % 180 == 60 {
|
||||
if tick_number % 1800 == 600 {
|
||||
BotsManager::check_pending_updates().await;
|
||||
}
|
||||
|
||||
tick_number = (tick_number + 1) % 180;
|
||||
tick_number = (tick_number + 1) % 1800;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user