Implement the Error
trait and improve our custom error
This commit is contained in:
parent
6c48eb11ac
commit
0a5a3d366b
10 changed files with 135 additions and 196 deletions
|
@ -1,7 +1,7 @@
|
||||||
//! Commons elements that are used in while executing the bot commands.
|
//! Commons elements that are used in while executing the bot commands.
|
||||||
|
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use std::sync::Arc;
|
use std::{fmt::Display, sync::Arc};
|
||||||
|
|
||||||
use serenity::{
|
use serenity::{
|
||||||
async_trait,
|
async_trait,
|
||||||
|
@ -18,31 +18,65 @@ use crate::database::Client as DatabaseClient;
|
||||||
|
|
||||||
/// The kind of errors that can be returned while executing a command.
|
/// The kind of errors that can be returned while executing a command.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum CommandExecutionError {
|
pub enum Error {
|
||||||
/// An error that is returned when the `Serenity` crate failed to get the argument from the
|
/// An error that is returned when the `Serenity` crate failed to get the argument from the
|
||||||
/// Discord API payload.
|
/// Discord API payload.
|
||||||
ArgumentExtractionError(String),
|
InvalidCommand(String),
|
||||||
|
|
||||||
/// An error that is returned when the cast from the argument value to a Rust type failed.
|
/// An error that is returned when the cast from the argument value to a Rust type failed.
|
||||||
ArgumentDeserializationError(String),
|
ArgumentDeserialization(String),
|
||||||
|
|
||||||
/// The kind of error that is returned when a Database query failed.
|
/// The kind of error that is returned when a Database query failed.
|
||||||
DatabaseQueryError(String),
|
DatabaseQuery(String),
|
||||||
|
|
||||||
/// Error returned when we fail to extract the context of a Discord commands.
|
/// Error returned when we fail to extract the context of a Discord commands.
|
||||||
ContextRetrievalError(String),
|
ContextRetrieval(String),
|
||||||
|
|
||||||
/// Error returned when sending a command to the discord API failed.
|
/// Error returned when sending a command to the discord API failed.
|
||||||
DiscordAPICallError(String),
|
DiscordAPICall(serenity::Error),
|
||||||
|
|
||||||
/// Error returned when there was an issue while using a selector embed in the command
|
/// Error returned when there was an issue while using a selector embed in the command
|
||||||
/// response.
|
/// response.
|
||||||
SelectorError(String),
|
Selector(String),
|
||||||
|
|
||||||
/// Error returned when the given command is unknown to Yorokobot.
|
/// Error returned when the given command is unknown to Yorokobot.
|
||||||
UnknownCommand(String),
|
UnknownCommand(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let string = match self {
|
||||||
|
Error::InvalidCommand(e) => format!("Received an invalid command: {e}"),
|
||||||
|
Error::ArgumentDeserialization(e) => {
|
||||||
|
format!("Failed to deserialize command argument: {e}")
|
||||||
|
}
|
||||||
|
Error::DatabaseQuery(e) => format!("Failed to execute a database query: {e}"),
|
||||||
|
Error::ContextRetrieval(e) => {
|
||||||
|
format!("Failed to retrieve current session context: {e}")
|
||||||
|
}
|
||||||
|
Error::DiscordAPICall(e) => format!("Error during Discord API call: {e}"),
|
||||||
|
Error::Selector(e) => format!("Met an error while using an embed selector: {e}"),
|
||||||
|
Error::UnknownCommand(e) => format!("Met an unknown bot command: {e}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{string}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
match self {
|
||||||
|
Error::InvalidCommand(_) => None,
|
||||||
|
Error::ArgumentDeserialization(_) => None,
|
||||||
|
Error::DatabaseQuery(_) => None,
|
||||||
|
Error::ContextRetrieval(_) => None,
|
||||||
|
Error::DiscordAPICall(error) => error.source(),
|
||||||
|
Error::Selector(_) => None,
|
||||||
|
Error::UnknownCommand(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An option that can/have to be passed to a Discord command.
|
/// An option that can/have to be passed to a Discord command.
|
||||||
pub struct BotCommandOption {
|
pub struct BotCommandOption {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -67,11 +101,7 @@ pub trait BotCommand {
|
||||||
fn options_list() -> Vec<BotCommandOption>;
|
fn options_list() -> Vec<BotCommandOption>;
|
||||||
|
|
||||||
/// Execute the command with the given `context` and using the given `database`.
|
/// Execute the command with the given `context` and using the given `database`.
|
||||||
async fn run(
|
async fn run(&self, context: Context, database: Arc<DatabaseClient>) -> Result<(), Error>;
|
||||||
&self,
|
|
||||||
context: Context,
|
|
||||||
database: Arc<DatabaseClient>,
|
|
||||||
) -> Result<(), CommandExecutionError>;
|
|
||||||
|
|
||||||
/// Extract and deserialize the `index`th value from the command `options`.
|
/// Extract and deserialize the `index`th value from the command `options`.
|
||||||
fn extract_option_value(
|
fn extract_option_value(
|
||||||
|
|
|
@ -20,7 +20,7 @@ use crate::database::{
|
||||||
Client as DatabaseClient,
|
Client as DatabaseClient,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::commons::{BotCommand, BotCommandOption, CommandExecutionError};
|
use super::commons::{BotCommand, BotCommandOption, Error};
|
||||||
|
|
||||||
pub struct CreateTagCommand {
|
pub struct CreateTagCommand {
|
||||||
interaction: ApplicationCommandInteraction,
|
interaction: ApplicationCommandInteraction,
|
||||||
|
@ -50,25 +50,21 @@ impl BotCommand for CreateTagCommand {
|
||||||
CreateTagCommand { interaction }
|
CreateTagCommand { interaction }
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, context: Context, database: Arc<DatabaseClient>) -> Result<(), Error> {
|
||||||
&self,
|
|
||||||
context: Context,
|
|
||||||
database: Arc<DatabaseClient>,
|
|
||||||
) -> Result<(), CommandExecutionError> {
|
|
||||||
// Extract tag_name parameter
|
// Extract tag_name parameter
|
||||||
let tag_name = match self.interaction.data.options.first() {
|
let tag_name = match self.interaction.data.options.first() {
|
||||||
Some(a) => match &a.resolved {
|
Some(a) => match &a.resolved {
|
||||||
Some(r) => match r {
|
Some(r) => match r {
|
||||||
CommandDataOptionValue::String(r_str) => Ok(r_str),
|
CommandDataOptionValue::String(r_str) => Ok(r_str),
|
||||||
_ => Err(CommandExecutionError::ArgumentDeserializationError(
|
_ => Err(Error::ArgumentDeserialization(
|
||||||
"Received non String argument for the CreateTagCommand".to_string(),
|
"Received non String argument for the CreateTagCommand".to_string(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
None => Err(CommandExecutionError::ArgumentDeserializationError(
|
None => Err(Error::ArgumentDeserialization(
|
||||||
"Could not deserialize the argument for the CreateTagCommand".to_string(),
|
"Could not deserialize the argument for the CreateTagCommand".to_string(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
None => Err(CommandExecutionError::ArgumentExtractionError(
|
None => Err(Error::InvalidCommand(
|
||||||
"Failed to get the CreateTagCommand argument".to_string(),
|
"Failed to get the CreateTagCommand argument".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
@ -76,7 +72,7 @@ impl BotCommand for CreateTagCommand {
|
||||||
// Extract guild id from Serenity context
|
// Extract guild id from Serenity context
|
||||||
let guild_id = match self.interaction.guild_id {
|
let guild_id = match self.interaction.guild_id {
|
||||||
Some(a) => Ok(a.to_string()),
|
Some(a) => Ok(a.to_string()),
|
||||||
None => Err(CommandExecutionError::ContextRetrievalError(
|
None => Err(Error::ContextRetrieval(
|
||||||
"Could not fetch guild id from issued command".to_string(),
|
"Could not fetch guild id from issued command".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
@ -84,11 +80,11 @@ impl BotCommand for CreateTagCommand {
|
||||||
let guild = match database.get_by_id::<Guild>(&guild_id).await {
|
let guild = match database.get_by_id::<Guild>(&guild_id).await {
|
||||||
Ok(query) => match query {
|
Ok(query) => match query {
|
||||||
Some(r) => Ok(r),
|
Some(r) => Ok(r),
|
||||||
None => Err(CommandExecutionError::ContextRetrievalError(
|
None => Err(Error::ContextRetrieval(
|
||||||
"Failed to retrieve the guild where the command was issued".to_string(),
|
"Failed to retrieve the guild where the command was issued".to_string(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Err(()) => Err(CommandExecutionError::DatabaseQueryError(
|
Err(()) => Err(Error::DatabaseQuery(
|
||||||
"Could not access to the database".to_string(),
|
"Could not access to the database".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
@ -113,25 +109,19 @@ impl BotCommand for CreateTagCommand {
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => Ok(String::from("Tag successfully created.")),
|
Ok(_) => Ok(String::from("Tag successfully created.")),
|
||||||
Err(_) => Err(CommandExecutionError::DatabaseQueryError(
|
Err(_) => Err(Error::DatabaseQuery(
|
||||||
"Could not add new tag to the database".to_string(),
|
"Could not add new tag to the database".to_string(),
|
||||||
)),
|
)),
|
||||||
}?
|
}?
|
||||||
};
|
};
|
||||||
|
|
||||||
match self
|
self.interaction
|
||||||
.interaction
|
|
||||||
.create_interaction_response(context.http, |response| {
|
.create_interaction_response(context.http, |response| {
|
||||||
response
|
response
|
||||||
.kind(InteractionResponseType::ChannelMessageWithSource)
|
.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||||
.interaction_response_data(|message| message.content(response_content))
|
.interaction_response_data(|message| message.content(response_content))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
{
|
.map_err(Error::DiscordAPICall)
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(_e) => Err(CommandExecutionError::DiscordAPICallError(
|
|
||||||
"Failed to answer to the initial command".to_string(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ use serenity::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
commons::{BotCommandOption, CommandExecutionError},
|
commons::{BotCommandOption, Error},
|
||||||
BotCommand,
|
BotCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,31 +50,27 @@ impl BotCommand for DeleteTagCommand {
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, context: Context, database: Arc<DatabaseClient>) -> Result<(), Error> {
|
||||||
&self,
|
|
||||||
context: Context,
|
|
||||||
database: Arc<DatabaseClient>,
|
|
||||||
) -> Result<(), CommandExecutionError> {
|
|
||||||
let tag_name = match self.interaction.data.options.first() {
|
let tag_name = match self.interaction.data.options.first() {
|
||||||
Some(a) => match &a.resolved {
|
Some(a) => match &a.resolved {
|
||||||
Some(r) => match r {
|
Some(r) => match r {
|
||||||
CommandDataOptionValue::String(r_str) => Ok(r_str),
|
CommandDataOptionValue::String(r_str) => Ok(r_str),
|
||||||
_ => Err(CommandExecutionError::ArgumentDeserializationError(
|
_ => Err(Error::ArgumentDeserialization(
|
||||||
"Received non String argument for DeleteTagCommand".to_string(),
|
"Received non String argument for DeleteTagCommand".to_string(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
None => Err(CommandExecutionError::ArgumentDeserializationError(
|
None => Err(Error::ArgumentDeserialization(
|
||||||
"Failed to deserialize argument for DeleteTagCommand".to_string(),
|
"Failed to deserialize argument for DeleteTagCommand".to_string(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
None => Err(CommandExecutionError::ArgumentExtractionError(
|
None => Err(Error::InvalidCommand(
|
||||||
"Failed to find argument in DeleteTagCommand".to_string(),
|
"Failed to find argument in DeleteTagCommand".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
let guild_id = match self.interaction.guild_id {
|
let guild_id = match self.interaction.guild_id {
|
||||||
Some(r) => Ok(r.to_string()),
|
Some(r) => Ok(r.to_string()),
|
||||||
None => Err(CommandExecutionError::ContextRetrievalError(
|
None => Err(Error::ContextRetrieval(
|
||||||
"Failed to extract guild id from current context".to_string(),
|
"Failed to extract guild id from current context".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
@ -82,11 +78,11 @@ impl BotCommand for DeleteTagCommand {
|
||||||
let guild = match database.get_by_id::<Guild>(&guild_id).await {
|
let guild = match database.get_by_id::<Guild>(&guild_id).await {
|
||||||
Ok(query) => match query {
|
Ok(query) => match query {
|
||||||
Some(r) => Ok(r),
|
Some(r) => Ok(r),
|
||||||
None => Err(CommandExecutionError::ContextRetrievalError(
|
None => Err(Error::ContextRetrieval(
|
||||||
"Failed to retrieve the guild where the command was issued".to_string(),
|
"Failed to retrieve the guild where the command was issued".to_string(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Err(()) => Err(CommandExecutionError::DatabaseQueryError(
|
Err(()) => Err(Error::DatabaseQuery(
|
||||||
"Failed to access to the database".to_string(),
|
"Failed to access to the database".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
@ -104,7 +100,7 @@ impl BotCommand for DeleteTagCommand {
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => Ok(String::from("Successfully remove the tag")),
|
Ok(_) => Ok(String::from("Successfully remove the tag")),
|
||||||
Err(()) => Err(CommandExecutionError::DatabaseQueryError(
|
Err(()) => Err(Error::DatabaseQuery(
|
||||||
"Failed to remove tag from the database".to_string(),
|
"Failed to remove tag from the database".to_string(),
|
||||||
)),
|
)),
|
||||||
}?
|
}?
|
||||||
|
@ -112,18 +108,12 @@ impl BotCommand for DeleteTagCommand {
|
||||||
String::from("No matching tag for this server.")
|
String::from("No matching tag for this server.")
|
||||||
};
|
};
|
||||||
|
|
||||||
match self
|
self.interaction
|
||||||
.interaction
|
|
||||||
.create_interaction_response(context.http, |r| {
|
.create_interaction_response(context.http, |r| {
|
||||||
r.kind(InteractionResponseType::ChannelMessageWithSource)
|
r.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||||
.interaction_response_data(|message| message.content(response))
|
.interaction_response_data(|message| message.content(response))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
{
|
.map_err(Error::DiscordAPICall)
|
||||||
Ok(()) => Ok(()),
|
|
||||||
Err(_e) => Err(CommandExecutionError::DiscordAPICallError(
|
|
||||||
"Failed to answer the initial command".to_string(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use serenity::{
|
||||||
use crate::database::{models::Guild, Client as DatabaseClient};
|
use crate::database::{models::Guild, Client as DatabaseClient};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
commons::{BotCommandOption, CommandExecutionError},
|
commons::{BotCommandOption, Error},
|
||||||
BotCommand,
|
BotCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,14 +40,10 @@ impl BotCommand for ListTagCommand {
|
||||||
ListTagCommand { interaction }
|
ListTagCommand { interaction }
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, context: Context, database: Arc<DatabaseClient>) -> Result<(), Error> {
|
||||||
&self,
|
|
||||||
context: Context,
|
|
||||||
database: Arc<DatabaseClient>,
|
|
||||||
) -> Result<(), CommandExecutionError> {
|
|
||||||
let guild_id = match self.interaction.guild_id {
|
let guild_id = match self.interaction.guild_id {
|
||||||
Some(id) => Ok(id.to_string()),
|
Some(id) => Ok(id.to_string()),
|
||||||
None => Err(CommandExecutionError::ContextRetrievalError(
|
None => Err(Error::ContextRetrieval(
|
||||||
"Failed to extract guild id from current context".to_string(),
|
"Failed to extract guild id from current context".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
@ -55,11 +51,11 @@ impl BotCommand for ListTagCommand {
|
||||||
let guild = match database.get_by_id::<Guild>(&guild_id).await {
|
let guild = match database.get_by_id::<Guild>(&guild_id).await {
|
||||||
Ok(query) => match query {
|
Ok(query) => match query {
|
||||||
Some(r) => Ok(r),
|
Some(r) => Ok(r),
|
||||||
None => Err(CommandExecutionError::ContextRetrievalError(
|
None => Err(Error::ContextRetrieval(
|
||||||
"Failed to retrieve the guild where the command was issued".to_string(),
|
"Failed to retrieve the guild where the command was issued".to_string(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Err(()) => Err(CommandExecutionError::DatabaseQueryError(
|
Err(()) => Err(Error::DatabaseQuery(
|
||||||
"Failed to access to the database".to_string(),
|
"Failed to access to the database".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
@ -79,19 +75,13 @@ impl BotCommand for ListTagCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match self
|
self.interaction
|
||||||
.interaction
|
|
||||||
.create_interaction_response(context.http, |response| {
|
.create_interaction_response(context.http, |response| {
|
||||||
response
|
response
|
||||||
.kind(InteractionResponseType::ChannelMessageWithSource)
|
.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||||
.interaction_response_data(|message| message.content(response_content))
|
.interaction_response_data(|message| message.content(response_content))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
{
|
.map_err(Error::DiscordAPICall)
|
||||||
Ok(()) => Ok(()),
|
|
||||||
Err(_) => Err(CommandExecutionError::DiscordAPICallError(
|
|
||||||
"Failed to answer to the initial command".to_string(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::{
|
||||||
discord::message_builders::embed_builder::EmbedMessageBuilder,
|
discord::message_builders::embed_builder::EmbedMessageBuilder,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::commons::{BotCommand, BotCommandOption, CommandExecutionError};
|
use super::commons::{BotCommand, BotCommandOption, Error};
|
||||||
|
|
||||||
pub struct SourceCodeCommand {
|
pub struct SourceCodeCommand {
|
||||||
interaction: ApplicationCommandInteraction,
|
interaction: ApplicationCommandInteraction,
|
||||||
|
@ -39,14 +39,9 @@ impl BotCommand for SourceCodeCommand {
|
||||||
SourceCodeCommand { interaction }
|
SourceCodeCommand { interaction }
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, context: Context, _: Arc<DatabaseClient>) -> Result<(), Error> {
|
||||||
&self,
|
|
||||||
context: Context,
|
|
||||||
_: Arc<DatabaseClient>,
|
|
||||||
) -> Result<(), CommandExecutionError> {
|
|
||||||
let embed_builder = EmbedMessageBuilder::new(&context).await?;
|
let embed_builder = EmbedMessageBuilder::new(&context).await?;
|
||||||
match self
|
self.interaction
|
||||||
.interaction
|
|
||||||
.create_interaction_response(context.http, |response| {
|
.create_interaction_response(context.http, |response| {
|
||||||
response
|
response
|
||||||
.kind(InteractionResponseType::ChannelMessageWithSource)
|
.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||||
|
@ -55,11 +50,6 @@ impl BotCommand for SourceCodeCommand {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
{
|
.map_err(Error::DiscordAPICall)
|
||||||
Ok(()) => Ok(()),
|
|
||||||
Err(_e) => Err(CommandExecutionError::DiscordAPICallError(
|
|
||||||
"Failed to answer to the issued command".to_string(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use serenity::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
commons::{BotCommandOption, CommandExecutionError},
|
commons::{BotCommandOption, Error},
|
||||||
BotCommand,
|
BotCommand,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -43,26 +43,18 @@ impl BotCommand for SubscribeCommand {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, context: Context, database: Arc<DatabaseClient>) -> Result<(), Error> {
|
||||||
&self,
|
|
||||||
context: Context,
|
|
||||||
database: Arc<DatabaseClient>,
|
|
||||||
) -> Result<(), CommandExecutionError> {
|
|
||||||
self.interaction
|
self.interaction
|
||||||
.create_interaction_response(&context.http, |response| {
|
.create_interaction_response(&context.http, |response| {
|
||||||
response.kind(InteractionResponseType::DeferredChannelMessageWithSource)
|
response.kind(InteractionResponseType::DeferredChannelMessageWithSource)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_e| {
|
.map_err(Error::DiscordAPICall)?;
|
||||||
CommandExecutionError::DiscordAPICallError(
|
|
||||||
"Failed to answer with a temporary response".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let user_id = self.interaction.user.id.to_string();
|
let user_id = self.interaction.user.id.to_string();
|
||||||
let guild_id = match self.interaction.guild_id {
|
let guild_id = match self.interaction.guild_id {
|
||||||
Some(id) => Ok(id.to_string()),
|
Some(id) => Ok(id.to_string()),
|
||||||
None => Err(CommandExecutionError::ContextRetrievalError(
|
None => Err(Error::ContextRetrieval(
|
||||||
"Failed to extract guild id from current context".to_string(),
|
"Failed to extract guild id from current context".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
@ -70,11 +62,11 @@ impl BotCommand for SubscribeCommand {
|
||||||
let guild = match database.get_by_id::<Guild>(&guild_id).await {
|
let guild = match database.get_by_id::<Guild>(&guild_id).await {
|
||||||
Ok(query) => match query {
|
Ok(query) => match query {
|
||||||
Some(r) => Ok(r),
|
Some(r) => Ok(r),
|
||||||
None => Err(CommandExecutionError::ContextRetrievalError(
|
None => Err(Error::ContextRetrieval(
|
||||||
"Failed to retrieve the guild where the command was issued".to_string(),
|
"Failed to retrieve the guild where the command was issued".to_string(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Err(()) => Err(CommandExecutionError::DatabaseQueryError(
|
Err(()) => Err(Error::DatabaseQuery(
|
||||||
"Failed to access to the database".to_string(),
|
"Failed to access to the database".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
@ -117,7 +109,7 @@ impl BotCommand for SubscribeCommand {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
CommandExecutionError::DatabaseQueryError(
|
Error::DatabaseQuery(
|
||||||
"Failed to update user subscriptions in database".to_string(),
|
"Failed to update user subscriptions in database".to_string(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
@ -125,25 +117,20 @@ impl BotCommand for SubscribeCommand {
|
||||||
|
|
||||||
let mut response = match self.interaction.get_interaction_response(&context).await {
|
let mut response = match self.interaction.get_interaction_response(&context).await {
|
||||||
Ok(r) => Ok(r),
|
Ok(r) => Ok(r),
|
||||||
Err(_e) => Err(CommandExecutionError::ContextRetrievalError(
|
Err(_e) => Err(Error::ContextRetrieval(
|
||||||
"Failed to fetch initial interaction response".to_string(),
|
"Failed to fetch initial interaction response".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
response.delete_reactions(&context).await.map_err(|_e| {
|
response
|
||||||
CommandExecutionError::DiscordAPICallError(
|
.delete_reactions(&context)
|
||||||
"Failed to remove reactions from initial response".to_string(),
|
.await
|
||||||
)
|
.map_err(Error::DiscordAPICall)?;
|
||||||
})?;
|
|
||||||
|
|
||||||
response
|
response
|
||||||
.edit(&context, |msg| msg.suppress_embeds(true).content("Done !"))
|
.edit(&context, |msg| msg.suppress_embeds(true).content("Done !"))
|
||||||
.await
|
.await
|
||||||
.map_err(|_e| {
|
.map_err(Error::DiscordAPICall)?;
|
||||||
CommandExecutionError::DiscordAPICallError(
|
|
||||||
"Failed to edit content of the original response message".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ use serenity::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
commons::{BotCommandOption, CommandExecutionError},
|
commons::{BotCommandOption, Error},
|
||||||
BotCommand,
|
BotCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,25 +46,17 @@ impl BotCommand for TagNotifyCommand {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(&self, context: Context, database: Arc<DatabaseClient>) -> Result<(), Error> {
|
||||||
&self,
|
|
||||||
context: Context,
|
|
||||||
database: Arc<DatabaseClient>,
|
|
||||||
) -> Result<(), CommandExecutionError> {
|
|
||||||
self.interaction
|
self.interaction
|
||||||
.create_interaction_response(&context.http, |response| {
|
.create_interaction_response(&context.http, |response| {
|
||||||
response.kind(InteractionResponseType::DeferredChannelMessageWithSource)
|
response.kind(InteractionResponseType::DeferredChannelMessageWithSource)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_e| {
|
.map_err(Error::DiscordAPICall)?;
|
||||||
CommandExecutionError::DiscordAPICallError(
|
|
||||||
"Failed to answer with a temporary response".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let guild_id = match self.interaction.guild_id {
|
let guild_id = match self.interaction.guild_id {
|
||||||
Some(id) => Ok(id.to_string()),
|
Some(id) => Ok(id.to_string()),
|
||||||
None => Err(CommandExecutionError::ContextRetrievalError(
|
None => Err(Error::ContextRetrieval(
|
||||||
"Failed to extract guild id from current context".to_string(),
|
"Failed to extract guild id from current context".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
@ -72,11 +64,11 @@ impl BotCommand for TagNotifyCommand {
|
||||||
let guild = match database.get_by_id::<Guild>(&guild_id).await {
|
let guild = match database.get_by_id::<Guild>(&guild_id).await {
|
||||||
Ok(query) => match query {
|
Ok(query) => match query {
|
||||||
Some(r) => Ok(r),
|
Some(r) => Ok(r),
|
||||||
None => Err(CommandExecutionError::ContextRetrievalError(
|
None => Err(Error::ContextRetrieval(
|
||||||
"Failed to retrieve the guild where the command was issued".to_string(),
|
"Failed to retrieve the guild where the command was issued".to_string(),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Err(()) => Err(CommandExecutionError::DatabaseQueryError(
|
Err(()) => Err(Error::DatabaseQuery(
|
||||||
"Failed to access to the database".to_string(),
|
"Failed to access to the database".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
@ -104,7 +96,7 @@ impl BotCommand for TagNotifyCommand {
|
||||||
for selected_tag in selection {
|
for selected_tag in selection {
|
||||||
let t = match guild.tags.iter().find(|s| s.name == selected_tag) {
|
let t = match guild.tags.iter().find(|s| s.name == selected_tag) {
|
||||||
Some(t) => Ok(t),
|
Some(t) => Ok(t),
|
||||||
None => Err(CommandExecutionError::ArgumentExtractionError(
|
None => Err(Error::InvalidCommand(
|
||||||
"No matching tag found for selection".to_string(),
|
"No matching tag found for selection".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
@ -128,16 +120,15 @@ impl BotCommand for TagNotifyCommand {
|
||||||
|
|
||||||
let response = match self.interaction.get_interaction_response(&context).await {
|
let response = match self.interaction.get_interaction_response(&context).await {
|
||||||
Ok(r) => Ok(r),
|
Ok(r) => Ok(r),
|
||||||
Err(_e) => Err(CommandExecutionError::ContextRetrievalError(
|
Err(_e) => Err(Error::ContextRetrieval(
|
||||||
"Failed to fetch initial interaction response".to_string(),
|
"Failed to fetch initial interaction response".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
response.delete(&context).await.map_err(|_e| {
|
response
|
||||||
CommandExecutionError::DiscordAPICallError(
|
.delete(&context)
|
||||||
"Failed to remove the embed message".to_string(),
|
.await
|
||||||
)
|
.map_err(Error::DiscordAPICall)?;
|
||||||
})?;
|
|
||||||
|
|
||||||
// We have to create a new message as editing the original response will not notify
|
// We have to create a new message as editing the original response will not notify
|
||||||
// the pinged users
|
// the pinged users
|
||||||
|
@ -150,20 +141,12 @@ impl BotCommand for TagNotifyCommand {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_e| {
|
.map_err(Error::DiscordAPICall)?;
|
||||||
CommandExecutionError::DiscordAPICallError(
|
|
||||||
"Failed to create a new message to ping users".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
} else {
|
} else {
|
||||||
self.interaction
|
self.interaction
|
||||||
.delete_original_interaction_response(&context)
|
.delete_original_interaction_response(&context)
|
||||||
.await
|
.await
|
||||||
.map_err(|_e| {
|
.map_err(Error::DiscordAPICall)?;
|
||||||
CommandExecutionError::DiscordAPICallError(
|
|
||||||
"Failed to delete the original interaction message".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -8,7 +8,7 @@ use super::commands::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::{models::Guild, Client as DatabaseClient},
|
database::{models::Guild, Client as DatabaseClient},
|
||||||
discord::commands::{commons::CommandExecutionError, DeleteTagCommand, SubscribeCommand},
|
discord::commands::{commons::Error, DeleteTagCommand, SubscribeCommand},
|
||||||
};
|
};
|
||||||
use serenity::{
|
use serenity::{
|
||||||
async_trait,
|
async_trait,
|
||||||
|
@ -153,7 +153,7 @@ impl EventHandler for Handler {
|
||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
drop(users_selector);
|
drop(users_selector);
|
||||||
Err(CommandExecutionError::SelectorError(
|
Err(Error::Selector(
|
||||||
"User has already a selector running".to_string(),
|
"User has already a selector running".to_string(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -170,13 +170,13 @@ impl EventHandler for Handler {
|
||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
drop(users_selector);
|
drop(users_selector);
|
||||||
Err(CommandExecutionError::SelectorError(
|
Err(Error::Selector(
|
||||||
"User has already a selector running".to_string(),
|
"User has already a selector running".to_string(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => Err(CommandExecutionError::UnknownCommand(
|
_ => Err(Error::UnknownCommand(
|
||||||
"Received an unknown command from Discord".to_string(),
|
"Received an unknown command from Discord".to_string(),
|
||||||
)),
|
)),
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,7 +5,7 @@ use serenity::{
|
||||||
prelude::Context,
|
prelude::Context,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::discord::commands::commons::CommandExecutionError;
|
use crate::discord::commands::commons::Error;
|
||||||
|
|
||||||
const HTML_COLOR_CODE: u32 = 0xffffff;
|
const HTML_COLOR_CODE: u32 = 0xffffff;
|
||||||
|
|
||||||
|
@ -16,12 +16,11 @@ pub struct EmbedMessageBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EmbedMessageBuilder {
|
impl EmbedMessageBuilder {
|
||||||
pub async fn new(context: &Context) -> Result<Self, CommandExecutionError> {
|
pub async fn new(context: &Context) -> Result<Self, Error> {
|
||||||
let bot_user = context.http.get_current_user().await.map_err(|_e| {
|
let bot_user =
|
||||||
CommandExecutionError::ContextRetrievalError(
|
context.http.get_current_user().await.map_err(|_e| {
|
||||||
"Failed to get current bot user".to_string(),
|
Error::ContextRetrieval("Failed to get current bot user".to_string())
|
||||||
)
|
})?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let embed_author = bot_user.name.clone();
|
let embed_author = bot_user.name.clone();
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ use serenity::{
|
||||||
prelude::Context,
|
prelude::Context,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::discord::commands::commons::CommandExecutionError;
|
use crate::discord::commands::commons::Error;
|
||||||
|
|
||||||
use super::embed_builder::EmbedMessageBuilder;
|
use super::embed_builder::EmbedMessageBuilder;
|
||||||
|
|
||||||
|
@ -83,9 +83,7 @@ impl<'a> EmbedSelector<'a> {
|
||||||
selector
|
selector
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_user_selection(
|
pub async fn get_user_selection(&mut self) -> Result<Option<HashSet<String>>, Error> {
|
||||||
&mut self,
|
|
||||||
) -> Result<Option<HashSet<String>>, CommandExecutionError> {
|
|
||||||
let embed_builder = EmbedMessageBuilder::new(self.context).await?;
|
let embed_builder = EmbedMessageBuilder::new(self.context).await?;
|
||||||
|
|
||||||
match self
|
match self
|
||||||
|
@ -107,19 +105,16 @@ impl<'a> EmbedSelector<'a> {
|
||||||
self.display_reactions().await?;
|
self.display_reactions().await?;
|
||||||
Ok(self.wait_selector_end().await?)
|
Ok(self.wait_selector_end().await?)
|
||||||
}
|
}
|
||||||
Err(_e) => Err(CommandExecutionError::DiscordAPICallError(
|
Err(e) => Err(Error::DiscordAPICall(e)),
|
||||||
"Failed to edit original interaction response".to_string(),
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn display_reactions(&self) -> Result<(), CommandExecutionError> {
|
async fn display_reactions(&self) -> Result<(), Error> {
|
||||||
if let Some(answer) = &self.embed_answer {
|
if let Some(answer) = &self.embed_answer {
|
||||||
answer.delete_reactions(self.context).await.map_err(|_e| {
|
answer
|
||||||
CommandExecutionError::DiscordAPICallError(
|
.delete_reactions(self.context)
|
||||||
"Failed to delete reaction on the current selector".to_string(),
|
.await
|
||||||
)
|
.map_err(Error::DiscordAPICall)?;
|
||||||
})?;
|
|
||||||
|
|
||||||
for emote in SELECTION_EMOTES[0..self.get_current_page_choice_number()]
|
for emote in SELECTION_EMOTES[0..self.get_current_page_choice_number()]
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -137,30 +132,24 @@ impl<'a> EmbedSelector<'a> {
|
||||||
Some(a) => a
|
Some(a) => a
|
||||||
.react(self.context, ReactionType::Unicode(emote.to_string()))
|
.react(self.context, ReactionType::Unicode(emote.to_string()))
|
||||||
.await
|
.await
|
||||||
.map_err(|_e| {
|
.map_err(Error::DiscordAPICall),
|
||||||
CommandExecutionError::DiscordAPICallError(
|
None => Err(Error::Selector(
|
||||||
"Failed to add reactions on the current selector".to_string(),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
None => Err(CommandExecutionError::SelectorError(
|
|
||||||
"Failed to refresh the reactions of the current selector".to_string(),
|
"Failed to refresh the reactions of the current selector".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(CommandExecutionError::SelectorError(
|
Err(Error::Selector(
|
||||||
"Tried to delete reaction from a non existent message".to_string(),
|
"Tried to delete reaction from a non existent message".to_string(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_selector_end(
|
async fn wait_selector_end(&mut self) -> Result<Option<HashSet<String>>, Error> {
|
||||||
&mut self,
|
|
||||||
) -> Result<Option<HashSet<String>>, CommandExecutionError> {
|
|
||||||
let answer = match &self.embed_answer {
|
let answer = match &self.embed_answer {
|
||||||
Some(a) => Ok(a),
|
Some(a) => Ok(a),
|
||||||
None => Err(CommandExecutionError::SelectorError(
|
None => Err(Error::Selector(
|
||||||
"Tried to start collector before sending it".to_string(),
|
"Tried to start collector before sending it".to_string(),
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
@ -171,11 +160,7 @@ impl<'a> EmbedSelector<'a> {
|
||||||
.add_message_id(*answer.id.as_u64())
|
.add_message_id(*answer.id.as_u64())
|
||||||
.timeout(Duration::from_secs(COLLECTOR_MAX_DURATION_SEC))
|
.timeout(Duration::from_secs(COLLECTOR_MAX_DURATION_SEC))
|
||||||
.build()
|
.build()
|
||||||
.map_err(|_e| {
|
.map_err(|_e| Error::Selector("Failed to build the EventCollector".to_string()))?;
|
||||||
CommandExecutionError::SelectorError(
|
|
||||||
"Failed to build the EventCollector".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
while let Some(reaction_event) = collector.next().await {
|
while let Some(reaction_event) = collector.next().await {
|
||||||
let reaction = match *reaction_event {
|
let reaction = match *reaction_event {
|
||||||
|
@ -232,11 +217,10 @@ impl<'a> EmbedSelector<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reaction.delete(self.context).await.map_err(|_e| {
|
reaction
|
||||||
CommandExecutionError::DiscordAPICallError(
|
.delete(self.context)
|
||||||
"Failed to delete reaction from selector".to_string(),
|
.await
|
||||||
)
|
.map_err(Error::DiscordAPICall)?;
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
collector.stop();
|
collector.stop();
|
||||||
|
@ -248,7 +232,7 @@ impl<'a> EmbedSelector<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn next_page(&mut self) -> Result<(), CommandExecutionError> {
|
async fn next_page(&mut self) -> Result<(), Error> {
|
||||||
if self.current_page != self.page_number {
|
if self.current_page != self.page_number {
|
||||||
self.current_page += 1;
|
self.current_page += 1;
|
||||||
self.refresh_embed_selection().await?;
|
self.refresh_embed_selection().await?;
|
||||||
|
@ -256,7 +240,7 @@ impl<'a> EmbedSelector<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn previous_page(&mut self) -> Result<(), CommandExecutionError> {
|
async fn previous_page(&mut self) -> Result<(), Error> {
|
||||||
if self.current_page != 1 {
|
if self.current_page != 1 {
|
||||||
self.current_page -= 1;
|
self.current_page -= 1;
|
||||||
self.refresh_embed_selection().await?;
|
self.refresh_embed_selection().await?;
|
||||||
|
@ -274,7 +258,7 @@ impl<'a> EmbedSelector<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn refresh_embed_selection(&mut self) -> Result<(), CommandExecutionError> {
|
async fn refresh_embed_selection(&mut self) -> Result<(), Error> {
|
||||||
let embed_builder = EmbedMessageBuilder::new(self.context).await?;
|
let embed_builder = EmbedMessageBuilder::new(self.context).await?;
|
||||||
|
|
||||||
let curr_choices = self.selectable
|
let curr_choices = self.selectable
|
||||||
|
@ -295,10 +279,6 @@ impl<'a> EmbedSelector<'a> {
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_e| {
|
.map_err(Error::DiscordAPICall)
|
||||||
CommandExecutionError::DiscordAPICallError(
|
|
||||||
"Failed to edit selector content".to_string(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue