diff --git a/src/client.rs b/src/client.rs index 7c44fa8..36cfe91 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, Mutex}; + use serenity::{prelude::GatewayIntents, Client as SerenityClient}; use crate::{database::Client as DatabaseClient, environment::get_env_variable}; @@ -6,18 +8,19 @@ use crate::discord::event_handler::Handler; pub struct Client { serenity_client: SerenityClient, + database_client: Arc>, } impl Client { pub async fn new() -> Self { - let mut database_client = DatabaseClient::new(); - database_client.connect().await; + let database_client = Arc::new(Mutex::new(DatabaseClient::new())); + database_client.clone().lock().unwrap().connect(); let discord_token = get_env_variable("DISCORD_TOKEN"); let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT; let event_handler = Handler { - database: database_client, + database: database_client.clone(), }; let serenity_client = match SerenityClient::builder(discord_token, intents) @@ -28,7 +31,10 @@ impl Client { Err(e) => panic!("Failed to instantiate Discord Client: {e}"), }; - Client { serenity_client } + Client { + serenity_client, + database_client, + } } pub async fn start(&mut self) { diff --git a/src/discord/commands.rs b/src/discord/commands.rs index d5bcb87..6791455 100644 --- a/src/discord/commands.rs +++ b/src/discord/commands.rs @@ -1,5 +1,6 @@ -pub mod bulk_create_tag; -pub mod create_tag; -pub mod delete_tag; -pub mod list_tags; -pub mod source_code; +mod bulk_create_tag; +pub mod commands; +mod create_tag; +mod delete_tag; +mod list_tags; +mod source_code; diff --git a/src/discord/commands/commands.rs b/src/discord/commands/commands.rs new file mode 100644 index 0000000..f88846b --- /dev/null +++ b/src/discord/commands/commands.rs @@ -0,0 +1,49 @@ +use serenity::{ + async_trait, + builder::CreateInteractionResponseData, + model::prelude::{ + command::{Command, CommandOptionType}, + interaction::application_command::ApplicationCommandInteraction, + }, + prelude::Context, +}; + +use crate::database::Client as DatabaseClient; + +pub struct BotCommandOption { + pub name: String, + pub description: String, + pub kind: CommandOptionType, + pub required: bool, +} + +#[async_trait] +pub trait BotCommand { + fn new(context: ApplicationCommandInteraction) -> Self; + fn name() -> String; + fn description() -> String; + fn options_list() -> Vec; + async fn run(&self, response: &mut CreateInteractionResponseData, database: &DatabaseClient); + + async fn register(context: &Context) { + match Command::create_global_application_command(context, |command| { + let mut new_command = command.name(Self::name()).description(Self::description()); + + for opt in Self::options_list() { + new_command = new_command.create_option(|option| { + option + .name(opt.name) + .description(opt.description) + .kind(opt.kind) + .required(opt.required) + }); + } + new_command + }) + .await + { + Ok(_) => println!("Successfully registered the {} command", Self::name()), + Err(_) => panic!("Failed to register the {} command", Self::name()), + }; + } +} diff --git a/src/discord/commands/create_tag.rs b/src/discord/commands/create_tag.rs index 64f9993..231100e 100644 --- a/src/discord/commands/create_tag.rs +++ b/src/discord/commands/create_tag.rs @@ -1,7 +1,8 @@ use mongodb::bson::doc; use serenity::{ - builder::{CreateApplicationCommand, CreateInteractionResponseData}, + async_trait, + builder::CreateInteractionResponseData, model::{ application::interaction::application_command::ApplicationCommandInteraction, prelude::{ @@ -12,61 +13,74 @@ use serenity::{ use crate::database::{models::Tag, Client as DatabaseClient}; -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command - .name("create_tag") - .description("Add a new tag") - .create_option(|option| { - option - .name("tag") - .description("The tag to create") - .kind(CommandOptionType::String) - .required(true) - }) +use super::commands::{BotCommand, BotCommandOption}; + +struct CreateTagCommand { + context: ApplicationCommandInteraction, } -pub async fn run<'a, 'b>( - response: &'a mut CreateInteractionResponseData<'b>, - command: &ApplicationCommandInteraction, - database: &DatabaseClient, -) -> &'a mut CreateInteractionResponseData<'b> { - let arg = command - .data - .options - .get(0) - .expect("Missing option") - .resolved - .as_ref() - .expect("Could not deserialize option"); - - let guild_id = command - .guild_id - .expect("Could not fetch guild id") - .to_string(); - - if let CommandDataOptionValue::String(tag_name) = arg { - let matching_tags = database - .get_all::(Some(doc! {"name": tag_name, "guild_id": guild_id.as_str()})) - .await; - - if !matching_tags.is_empty() { - response.content("This tag already exist for this server"); - } else { - match database - .insert_one(Tag { - id: None, - name: tag_name.to_string(), - guild_id: guild_id.clone(), - is_nsfw: false, - subscribers: vec![], - }) - .await - { - Ok(_) => response.content("Tag successfully created."), - Err(_) => response.content("Error creating the tag"), - }; - } +#[async_trait] +impl BotCommand for CreateTagCommand { + fn name() -> String { + String::from("create_tag") } - response + fn description() -> String { + String::from("Create a new tag") + } + + fn options_list() -> Vec { + vec![BotCommandOption { + name: String::from("tag"), + description: String::from("The tag to create"), + kind: CommandOptionType::String, + required: true, + }] + } + + fn new(context: ApplicationCommandInteraction) -> Self { + CreateTagCommand { context } + } + + async fn run(&self, response: &mut CreateInteractionResponseData, database: &DatabaseClient) { + let arg = self + .context + .data + .options + .get(0) + .expect("Missing option") + .resolved + .as_ref() + .expect("Could not deserialize option"); + + let guild_id = self + .context + .guild_id + .expect("Could not fetch guild id") + .to_string(); + + if let CommandDataOptionValue::String(tag_name) = arg { + let matching_tags = database + .get_all::(Some(doc! {"name": tag_name, "guild_id": guild_id.as_str()})) + .await; + + if !matching_tags.is_empty() { + response.content("This tag already exist for this server"); + } else { + match database + .insert_one(Tag { + id: None, + name: tag_name.to_string(), + guild_id: guild_id.clone(), + is_nsfw: false, + subscribers: vec![], + }) + .await + { + Ok(_) => response.content("Tag successfully created."), + Err(_) => response.content("Error creating the tag"), + }; + } + } + } } diff --git a/src/discord/event_handler.rs b/src/discord/event_handler.rs index f99c220..7bf826c 100644 --- a/src/discord/event_handler.rs +++ b/src/discord/event_handler.rs @@ -1,23 +1,17 @@ +use std::sync::{Arc, Mutex}; + use crate::database::Client as DatabaseClient; use serenity::{ async_trait, model::gateway::Ready, - model::{ - application::command::Command, - prelude::{ - interaction::{Interaction, InteractionResponseType}, - ResumedEvent, - }, - }, + model::prelude::{interaction::Interaction, ResumedEvent}, prelude::{Context, EventHandler}, }; -use super::commands::*; - const MAX_ARGS_NUMBER: u32 = 25; pub struct Handler { - pub database: DatabaseClient, + pub database: Arc>, } #[async_trait] @@ -25,21 +19,7 @@ impl EventHandler for Handler { async fn ready(&self, ctx: Context, ready: Ready) { println!("Successfully connected as {}", ready.user.name); - match Command::set_global_application_commands(&ctx.http, |commands| { - commands - .create_application_command(|command| source_code::register(command)) - .create_application_command(|command| create_tag::register(command)) - .create_application_command(|command| { - bulk_create_tag::register(command, MAX_ARGS_NUMBER) - }) - .create_application_command(|command| delete_tag::register(command)) - .create_application_command(|command| list_tags::register(command)) - }) - .await - { - Ok(_) => println!("Successfully registered application commands"), - Err(e) => println!("Failed to register application commands: {e}"), - }; + // TODO: Register commands } async fn resume(&self, _: Context, _: ResumedEvent) { @@ -49,21 +29,6 @@ impl EventHandler for Handler { async fn interaction_create(&self, ctx: Context, interaction: Interaction) { if let Interaction::ApplicationCommand(command) = interaction { println!("Received command {}", command.data.name); - - if let Err(e) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| match command.data.name.as_str() { - "source_code" => source_code::run(message), - "create_tag" => create_tag::run(message, &command, &self.database), - _ => message.content("Not yet implemented"), - }) - }) - .await - { - println!("Failed to answer to command: {e}"); - } } } }