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:
parent
c1f76f8946
commit
a72f0ee43d
4 changed files with 143 additions and 35 deletions
|
@ -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,
|
||||||
|
|
|
@ -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
12
src/utils/italian.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue