7 Commits

Author SHA1 Message Date
fc10c4c576 Log backtrace and error chain in custom handler
Some checks failed
Build docker image / Build-Docker-Image (push) Has been cancelled
rust-clippy analyze / Run rust-clippy analyzing (push) Has been cancelled
2025-10-26 19:28:25 +01:00
359a6b6137 Add CustomErrorHandler and use it
Some checks are pending
Build docker image / Build-Docker-Image (push) Waiting to run
rust-clippy analyze / Run rust-clippy analyzing (push) Waiting to run
Ignore benign 'Bad Request: message to be replied not found' Telegram
error while logging other errors
2025-10-25 20:16:19 +02:00
17ef8a7f3d Handle AlreadyExists (409) during registration 2025-10-25 19:51:43 +02:00
0d028b6a66 Add helper to skip redundant message edits 2025-10-25 19:15:16 +02:00
9fb550404e Remove unused Source struct from book library types
Some checks failed
Build docker image / Build-Docker-Image (push) Has been cancelled
rust-clippy analyze / Run rust-clippy analyzing (push) Has been cancelled
2025-10-14 17:48:16 +02:00
07c725e0df Merge pull request #42 from flibusta-apps/dependabot/github_actions/github/codeql-action-4
Bump github/codeql-action from 3 to 4
2025-10-14 17:43:04 +02:00
dependabot[bot]
6f2de597b4 Bump github/codeql-action from 3 to 4
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-08 11:05:43 +00:00
14 changed files with 172 additions and 33 deletions

View File

@@ -49,7 +49,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: Upload analysis results to GitHub - name: Upload analysis results to GitHub
uses: github/codeql-action/upload-sarif@v3 uses: github/codeql-action/upload-sarif@v4
with: with:
sarif_file: rust-clippy-results.sarif sarif_file: rust-clippy-results.sarif
wait-for-processing: true wait-for-processing: true

View File

@@ -18,7 +18,9 @@ use tokio_util::compat::FuturesAsyncReadCompatExt;
use crate::bots::{ use crate::bots::{
approved_bot::{ 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}, services::book_library::{get_author_annotation, get_book_annotation},
tools::filter_callback_query, tools::filter_callback_query,
}, },
@@ -145,17 +147,24 @@ where
} else { } else {
chunked_text.len() 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 = let keyboard =
generic_get_pagination_keyboard(page, chunked_text.len().try_into()?, callback_data, false); 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) .reply_markup(keyboard)
.send() .send()
.await?; .await
{
Ok(()) Ok(_) => Ok(()),
Err(err) => Err(err.into()),
}
} }
pub fn get_annotations_handler() -> crate::bots::BotHandler { pub fn get_annotations_handler() -> crate::bots::BotHandler {

View File

@@ -15,6 +15,7 @@ use teloxide::{
use tracing::log; use tracing::log;
use crate::bots::approved_bot::{ use crate::bots::approved_bot::{
modules::utils::message_text::is_message_text_equals,
services::{ services::{
book_library::{ book_library::{
formatters::{Format, FormatTitle}, formatters::{Format, FormatTitle},
@@ -176,12 +177,19 @@ where
let keyboard = generic_get_pagination_keyboard(page, items_page.pages, callback_data, true); let keyboard = generic_get_pagination_keyboard(page, items_page.pages, callback_data, true);
bot.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) .reply_markup(keyboard)
.send() .send()
.await?; .await
{
Ok(()) Ok(_) => Ok(()),
Err(err) => Err(err.into()),
}
} }
pub fn get_book_handler() -> crate::bots::BotHandler { pub fn get_book_handler() -> crate::bots::BotHandler {

View File

@@ -14,6 +14,7 @@ use teloxide::{
use crate::bots::{ use crate::bots::{
approved_bot::{ approved_bot::{
modules::utils::message_text::is_message_text_equals,
services::{ services::{
book_library::{ book_library::{
formatters::{Format, FormatTitle}, formatters::{Format, FormatTitle},
@@ -45,7 +46,7 @@ where
let chat_id = cq.chat_id(); let chat_id = cq.chat_id();
let user_id = cq.from.id; let user_id = cq.from.id;
let message_id = cq.message.as_ref().map(|message| message.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) { 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),
@@ -106,15 +107,20 @@ where
} }
let formatted_page = items_page.format(page, 4096); 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); let keyboard = generic_get_pagination_keyboard(page, items_page.pages, search_data, true);
match bot
bot.edit_message_text(chat_id, message_id, formatted_page) .edit_message_text(chat_id, message_id, formatted_page)
.reply_markup(keyboard) .reply_markup(keyboard)
.send() .send()
.await?; .await
{
Ok(()) Ok(_) => Ok(()),
Err(err) => Err(err.into()),
}
} }
pub async fn message_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal { pub async fn message_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> BotHandlerInternal {

View File

@@ -4,7 +4,10 @@ pub mod commands;
use chrono::{prelude::*, Duration}; use chrono::{prelude::*, Duration};
use crate::bots::{ 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, BotHandlerInternal,
}; };
@@ -78,7 +81,7 @@ async fn update_log_pagination_handler(
bot: CacheMe<Throttle<Bot>>, bot: CacheMe<Throttle<Bot>>,
update_callback_data: UpdateLogCallbackData, update_callback_data: UpdateLogCallbackData,
) -> BotHandlerInternal { ) -> BotHandlerInternal {
let message = match cq.message { let message = match cq.message.clone() {
Some(v) => v, Some(v) => v,
None => { None => {
bot.send_message(cq.from.id, "Ошибка! Попробуйте заново(") 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 formatted_page = items_page.format(page, 4096);
let message_text = format!("{header}{formatted_page}"); 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); 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) .reply_markup(keyboard)
.send() .send()
.await?; .await
{
Ok(()) Ok(_) => Ok(()),
Err(err) => Err(err.into()),
}
} }
pub fn get_update_log_handler() -> crate::bots::BotHandler { pub fn get_update_log_handler() -> crate::bots::BotHandler {

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

View File

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

View File

@@ -32,12 +32,6 @@ impl BookGenre {
} }
} }
#[derive(Deserialize, Debug, Clone)]
pub struct Source {
// id: u32,
// name: String
}
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct Author { pub struct Author {
pub id: u32, pub id: u32,

View File

@@ -19,9 +19,10 @@ pub async fn message_handler(message: Message, bot: CacheMe<Throttle<Bot>>) -> a
let message_text = match result { let message_text = match result {
register::RegisterStatus::Success { ref username } => format_registered_message(username), 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::LimitExtended => strings::LIMIT_EXTENDED_MESSAGE.to_string(),
register::RegisterStatus::WrongToken => strings::ERROR_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) bot.send_message(message.chat.id, message_text)

View File

@@ -12,12 +12,14 @@ pub enum RegisterStatus {
WrongToken, WrongToken,
RegisterFail, RegisterFail,
LimitExtended, LimitExtended,
AlreadyExists,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum RegisterRequestStatus { pub enum RegisterRequestStatus {
Success, Success,
LimitExtended, LimitExtended,
AlreadyExists,
UnknownError, UnknownError,
} }
@@ -52,6 +54,7 @@ async fn make_register_request(
Ok(match result.status().as_u16() { Ok(match result.status().as_u16() {
200 => RegisterRequestStatus::Success, 200 => RegisterRequestStatus::Success,
402 => RegisterRequestStatus::LimitExtended, 402 => RegisterRequestStatus::LimitExtended,
409 => RegisterRequestStatus::AlreadyExists,
_ => RegisterRequestStatus::UnknownError, _ => RegisterRequestStatus::UnknownError,
}) })
} }
@@ -81,5 +84,6 @@ pub async fn register(user_id: UserId, message_text: &str) -> RegisterStatus {
}, },
RegisterRequestStatus::LimitExtended => RegisterStatus::LimitExtended, RegisterRequestStatus::LimitExtended => RegisterStatus::LimitExtended,
RegisterRequestStatus::UnknownError => RegisterStatus::RegisterFail, RegisterRequestStatus::UnknownError => RegisterStatus::RegisterFail,
RegisterRequestStatus::AlreadyExists => RegisterStatus::AlreadyExists,
} }
} }

View File

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

View 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(),
}
}
}

View File

@@ -1,6 +1,7 @@
use super::custom_error_handler::CustomErrorHandler;
use teloxide::adaptors::throttle::Limits; use teloxide::adaptors::throttle::Limits;
use teloxide::dispatching::Dispatcher; use teloxide::dispatching::Dispatcher;
use teloxide::error_handlers::LoggingErrorHandler;
use teloxide::requests::{Request, Requester, RequesterExt}; use teloxide::requests::{Request, Requester, RequesterExt};
use teloxide::stop::StopToken; use teloxide::stop::StopToken;
use teloxide::stop::{mk_stop_token, StopFlag}; use teloxide::stop::{mk_stop_token, StopFlag};
@@ -108,7 +109,7 @@ pub async fn start_bot(bot_data: &BotData) {
dispatcher dispatcher
.dispatch_with_listener( .dispatch_with_listener(
listener, listener,
LoggingErrorHandler::with_custom_text("An error from the update listener"), CustomErrorHandler::with_custom_text("An error from the update listener"),
) )
.await; .await;
}); });

View File

@@ -1,6 +1,7 @@
pub mod axum_server; pub mod axum_server;
pub mod bot_manager_client; pub mod bot_manager_client;
pub mod closable_sender; pub mod closable_sender;
pub mod custom_error_handler;
pub mod internal; pub mod internal;
pub mod utils; pub mod utils;