1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-25 04:24:20 +00:00

Differentiate between unknown command and parse error (#14)

Co-authored-by: Stefano Pigozzi <me@steffo.eu>
This commit is contained in:
Lorenzo Rossi 2024-08-25 12:49:29 +02:00 committed by GitHub
parent c1f76f8946
commit a72f0ee43d
Signed by: github
GPG key ID: B5690EEEBB952194
4 changed files with 143 additions and 35 deletions

View file

@ -4,14 +4,15 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::{Context, Error}; use anyhow::{Context, Error};
use teloxide::Bot;
use teloxide::payloads::SendMessageSetters; use teloxide::payloads::SendMessageSetters;
use teloxide::requests::Requester; use teloxide::requests::Requester;
use teloxide::types::{Message, ReplyParameters}; use teloxide::types::{Message, ReplyParameters};
use teloxide::utils::command::BotCommands; use teloxide::utils::command::BotCommands;
use teloxide::Bot;
use crate::services::telegram::dependencies::interface_database::DatabaseInterface; use crate::services::telegram::dependencies::interface_database::DatabaseInterface;
use crate::utils::anyhow_result::AnyResult; use crate::utils::anyhow_result::AnyResult;
use crate::utils::italian::countable_noun_suffix;
pub mod start; pub mod start;
pub mod fortune; pub mod fortune;
@ -111,7 +112,7 @@ impl Command {
log::trace!("Delegating error handling to error handler..."); log::trace!("Delegating error handling to error handler...");
let result2 = match result1.as_ref() { let result2 = match result1.as_ref() {
Ok(_) => return, Ok(_) => return,
Err(e1) => self.handle_error(&bot, &message, e1).await Err(e1) => self.handle_error_command(&bot, &message, e1).await
}; };
let e1 = result1.unwrap_err(); let e1 = result1.unwrap_err();
@ -129,12 +130,49 @@ impl Command {
Ok(()) Ok(())
} }
pub async fn handle_unknown(bot: Bot, message: Message) -> CommandResult { pub async fn handle_malformed_simple(bot: Bot, message: Message, expected: usize, found: usize) -> CommandResult {
log::debug!("Received an unknown command or an invalid syntax: {:?}", message.text()); log::debug!("Received a malformed command: {:?}", message.text());
log::trace!("Sending error message..."); log::trace!("Sending error message...");
let text = format!(
"⚠️ Il comando si aspetta {} argoment{}, ma ne ha ricevut{} solo {}.",
expected,
countable_noun_suffix(expected, "o", "i"),
countable_noun_suffix(found, "o", "i"),
found,
);
let _reply = bot let _reply = bot
.send_message(message.chat.id, "⚠️ Comando sconosciuto o sintassi non valida.") .send_message(message.chat.id, text)
.reply_parameters(ReplyParameters::new(message.id))
.await
.context("Non è stato possibile inviare il messaggio di errore.")?;
log::trace!("Successfully handled malformed command!");
Ok(())
}
pub async fn handle_malformed_complex(bot: Bot, message: Message) -> CommandResult {
log::debug!("Received a malformed command: {:?}", message.text());
log::trace!("Sending error message...");
let text = "⚠️ Il comando si aspetta una sintassi diversa da quella che ha ricevuto.";
let _reply = bot
.send_message(message.chat.id, text)
.reply_parameters(ReplyParameters::new(message.id))
.await
.context("Non è stato possibile inviare il messaggio di errore.")?;
log::trace!("Successfully handled malformed command!");
Ok(())
}
pub async fn handle_unknown(bot: Bot, message: Message) -> CommandResult {
log::debug!("Received an unknown command: {:?}", message.text());
log::trace!("Sending error message...");
let text = "⚠️ Il comando specificato non esiste.";
let _reply = bot
.send_message(message.chat.id, text)
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare il messaggio di errore.")?; .context("Non è stato possibile inviare il messaggio di errore.")?;
@ -143,7 +181,22 @@ impl Command {
Ok(()) Ok(())
} }
async fn handle_error(&self, bot: &Bot, message: &Message, error: &Error) -> CommandResult { pub async fn handle_error_parse(bot: &Bot, message: &Message, error: &Error) -> CommandResult {
log::debug!("Encountered a parsing error while parsing: {:?}", message.text());
log::trace!("Sending error message...");
let text = format!("⚠️ {error}");
let _reply = bot
.send_message(message.chat.id, text)
.reply_parameters(ReplyParameters::new(message.id))
.await
.context("Non è stato possibile inviare il messaggio di errore.")?;
log::trace!("Successfully handled malparsed command!");
Ok(())
}
pub async fn handle_error_command(&self, bot: &Bot, message: &Message, error: &Error) -> CommandResult {
log::debug!( log::debug!(
"Command message in {:?} with id {:?} and contents {:?} errored out with `{:?}`", "Command message in {:?} with id {:?} and contents {:?} errored out with `{:?}`",
&message.chat.id, &message.chat.id,
@ -153,8 +206,9 @@ impl Command {
); );
log::trace!("Sending error message..."); log::trace!("Sending error message...");
let text = format!("⚠️ {error}");
let _reply = bot let _reply = bot
.send_message(message.chat.id, format!("⚠️ {error}")) .send_message(message.chat.id, text)
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare il messaggio di errore.")?; .context("Non è stato possibile inviare il messaggio di errore.")?;
@ -163,7 +217,7 @@ impl Command {
Ok(()) Ok(())
} }
async fn handle_fatal(&self, _bot: &Bot, message: &Message, error1: &Error, error2: &Error) -> CommandResult { pub async fn handle_fatal(&self, _bot: &Bot, message: &Message, error1: &Error, error2: &Error) -> CommandResult {
log::error!( log::error!(
"Command message in {:?} with id {:?} and contents {:?} errored out with `{:?}`, and it was impossible to handle the error because of `{:?}`", "Command message in {:?} with id {:?} and contents {:?} errored out with `{:?}`, and it was impossible to handle the error because of `{:?}`",
&message.chat.id, &message.chat.id,

View file

@ -1,15 +1,14 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Context; use anyhow::Context;
use regex::Regex; use commands::Command;
use dependencies::interface_database::DatabaseInterface;
use keyboard_callbacks::KeyboardCallback;
use teloxide::dispatching::DefaultKey; use teloxide::dispatching::DefaultKey;
use teloxide::dptree::entry; use teloxide::dptree::entry;
use teloxide::prelude::*; use teloxide::prelude::*;
use teloxide::types::{Me, ParseMode}; use teloxide::types::{Me, ParseMode};
use teloxide::utils::command::{BotCommands, ParseError};
use commands::Command;
use dependencies::interface_database::DatabaseInterface;
use keyboard_callbacks::KeyboardCallback;
use crate::utils::anyhow_result::AnyResult; use crate::utils::anyhow_result::AnyResult;
use crate::utils::telegram_string::TelegramEscape; use crate::utils::telegram_string::TelegramEscape;
@ -102,16 +101,74 @@ impl TelegramService {
.context("Aggiornamento dei comandi del bot non riuscito.") .context("Aggiornamento dei comandi del bot non riuscito.")
} }
async fn handle_message(bot: Bot, me: Me, message: Message, database: Arc<DatabaseInterface>) -> AnyResult<()> {
log::debug!("Handling message: {message:#?}");
log::trace!("Accessing message text...");
let text = match message.text() {
None => {
log::trace!("Message has no text; skipping it.");
return Ok(())
}
Some(text) => {
log::trace!("Message has text: {text:?}");
text
}
};
log::trace!("Retrieving bot's username...");
let username = me.username();
log::trace!("Parsing message text {text:?} as {username:?}...");
let command = match Command::parse(text, username) {
Ok(command) => {
log::trace!("Message text parsed successfully as: {command:?}");
command
}
Err(ParseError::WrongBotName(receiver)) => {
log::debug!("Message is meant to be sent to {receiver:?}, while I'm running as {username:?}; skipping it.");
return Ok(());
}
Err(ParseError::TooFewArguments { expected, found, .. }) |
Err(ParseError::TooManyArguments { expected, found, .. }) => {
log::debug!("Message text is a command with {found} arguments, but the command expected {expected}; handling as a malformed command.");
Command::handle_malformed_simple(bot, message, expected, found).await
.context("Impossibile gestire comando malformato semplice.")?;
return Ok(());
}
Err(ParseError::IncorrectFormat(e)) => {
log::debug!("Message text is a command with a custom format, but the parser returned the error {e:?}; handling as a malformed command.");
Command::handle_malformed_complex(bot, message).await
.context("Impossibile gestire comando malformato complesso.")?;
return Ok(());
}
Err(ParseError::UnknownCommand(command)) => {
log::debug!("Message text is command not present in the commands list {command:?}; handling it as an unknown command.");
Command::handle_unknown(bot, message).await
.context("Impossibile gestire comando sconosciuto.")?;
return Ok(());
}
Err(ParseError::Custom(e)) => {
log::debug!("Message text is a command, but the parser raised custom error {e:?}; handling it as a custom error.");
let error = anyhow::format_err!(e);
Command::handle_error_parse(&bot, &message, &error).await
.context("Impossibile gestire comando con errore di parsing.")?;
return Ok(());
}
};
command.handle_self(bot, message, database).await
.context("Impossibile gestire errore restituito dal comando.")?;
Ok(())
}
fn dispatcher(&mut self) -> Dispatcher<Bot, anyhow::Error, DefaultKey> { fn dispatcher(&mut self) -> Dispatcher<Bot, anyhow::Error, DefaultKey> {
log::debug!("Building dispatcher..."); log::debug!("Building dispatcher...");
let bot_name = self.me.user.username.as_ref().unwrap(); let bot_name = self.me.user.username.as_ref().unwrap();
log::trace!("Bot username is: @{bot_name:?}"); log::trace!("Bot username is: @{bot_name:?}");
log::trace!("Determining pseudo-command regex...");
let regex = Regex::new(&format!(r"^/[a-z0-9_]+(?:@{bot_name})?(?:\s+.*)?$")).unwrap();
log::trace!("Pseudo-command regex is: {regex:?}");
let database = Arc::new(DatabaseInterface::new(self.database_url.clone())); let database = Arc::new(DatabaseInterface::new(self.database_url.clone()));
log::trace!("Building dispatcher..."); log::trace!("Building dispatcher...");
@ -121,24 +178,8 @@ impl TelegramService {
entry() entry()
// Messages // Messages
.branch(Update::filter_message() .branch(Update::filter_message()
// Pseudo-commands // Handle incoming messages
.branch(entry() .endpoint(Self::handle_message)
// Only process commands matching the pseudo-command regex
.filter(move |message: Message| -> bool {
message
.text()
.is_some_and(|text| regex.is_match(text))
})
// Commands
.branch(entry()
// Only process commands matching a valid command, and parse their arguments
.filter_command::<Command>()
// Delegate handling
.endpoint(Command::handle_self)
)
// No valid command was found
.endpoint(Command::handle_unknown)
)
) )
// Inline keyboard // Inline keyboard
.branch(Update::filter_callback_query() .branch(Update::filter_callback_query()

12
src/utils/italian.rs Normal file
View file

@ -0,0 +1,12 @@
use std::ops::Add;
pub fn countable_noun_suffix<T, U>(count: T, singular: &'static str, plural: &'static str)
-> &'static str
where
T: Default + Add<usize, Output=U> + PartialEq<U>,
{
match count == (T::default() + 1) {
true => singular,
false => plural,
}
}

View file

@ -4,3 +4,4 @@ pub mod time;
pub mod version; pub mod version;
pub mod anyhow_result; pub mod anyhow_result;
pub mod telegram_string; pub mod telegram_string;
pub mod italian;