diff --git a/src/client.rs b/src/client.rs index d9ad68b..7c44fa8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -6,16 +6,18 @@ use crate::discord::event_handler::Handler; pub struct Client { serenity_client: SerenityClient, - database_client: DatabaseClient, } -impl<'a> Client { - async fn new() -> Self { +impl Client { + pub async fn new() -> Self { + let mut database_client = DatabaseClient::new(); + database_client.connect().await; + let discord_token = get_env_variable("DISCORD_TOKEN"); let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT; - let database_client = DatabaseClient::new(); + let event_handler = Handler { - database: &database_client, + database: database_client, }; let serenity_client = match SerenityClient::builder(discord_token, intents) @@ -26,15 +28,10 @@ impl<'a> Client { Err(e) => panic!("Failed to instantiate Discord Client: {e}"), }; - Client { - serenity_client, - database_client, - } + Client { serenity_client } } pub async fn start(&mut self) { - self.database_client.connect().await; - if let Err(e) = self.serenity_client.start().await { panic!("Could not connect the bot: {e}"); } diff --git a/src/database.rs b/src/database.rs index e205e1b..e3a4ccb 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,6 +1,6 @@ //! Module for database interaction and wrapping pub mod client; -mod models; +pub mod models; pub use client::Client; diff --git a/src/database/client.rs b/src/database/client.rs index f18c28c..b41acf0 100644 --- a/src/database/client.rs +++ b/src/database/client.rs @@ -116,7 +116,7 @@ impl Client { pub async fn get_all Deserialize<'de>>( &self, filter: Option, - ) { + ) -> Vec { let mut result: Vec = vec![]; let mut cursor = match filter { @@ -129,6 +129,8 @@ impl Client { result .push(from_bson(Bson::Document(document)).expect("Could not deserialize document")); } + + return result; } #[allow(dead_code)] diff --git a/src/database/models.rs b/src/database/models.rs index b380bc3..9e5237c 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -5,46 +5,25 @@ use mongodb::bson::oid::ObjectId; use serde::{Deserialize, Serialize}; -pub const COLLECTIONS_NAMES: [&str; 2] = ["guilds", "tags"]; +pub const COLLECTIONS_NAMES: [&str; 1] = ["tags"]; pub trait YorokobotModel { fn get_collection_name() -> String; } -/// Settings for a server -#[derive(Debug, Serialize, Deserialize)] -pub struct GuildSettings { - admin_only_can_tag: bool, - server_ban_list: Vec, -} - -/// Server infos -#[derive(Debug, Serialize, Deserialize)] -pub struct Guild { - #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] - id: Option, - discord_guild_id: String, - bot_settings: GuildSettings, -} - /// Tags #[derive(Debug, Serialize, Deserialize)] pub struct Tag { #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] - id: Option, - name: String, - guild: String, - is_nsfw: bool, - subscribers: Vec, + pub id: Option, + pub name: String, + pub guild_id: String, + pub is_nsfw: bool, + pub subscribers: Vec, } -impl YorokobotModel for Guild { - fn get_collection_name() -> String { - "guilds".to_string() - } -} impl YorokobotModel for Tag { fn get_collection_name() -> String { - "traits".to_string() + "tags".to_string() } } diff --git a/src/discord.rs b/src/discord.rs index f0cc38c..c1f06ed 100644 --- a/src/discord.rs +++ b/src/discord.rs @@ -1 +1,3 @@ pub mod event_handler; + +mod commands; diff --git a/src/discord/commands.rs b/src/discord/commands.rs index e69de29..d5bcb87 100644 --- a/src/discord/commands.rs +++ b/src/discord/commands.rs @@ -0,0 +1,5 @@ +pub mod bulk_create_tag; +pub mod create_tag; +pub mod delete_tag; +pub mod list_tags; +pub mod source_code; diff --git a/src/discord/commands/bulk_create_tag.rs b/src/discord/commands/bulk_create_tag.rs new file mode 100644 index 0000000..07ce50c --- /dev/null +++ b/src/discord/commands/bulk_create_tag.rs @@ -0,0 +1,22 @@ +use serenity::{builder::CreateApplicationCommand, model::prelude::command::CommandOptionType}; + +pub fn register( + command: &mut CreateApplicationCommand, + max_args_number: u32, +) -> &mut CreateApplicationCommand { + command + .name("bulk_create_tag") + .description("Add multiples tags"); + + for i in 0..max_args_number { + command.create_option(|option| { + option + .name(format!("tag{}", i + 1)) + .description("A new tag to add") + .kind(CommandOptionType::String) + .required(i == 0) + }); + } + + command +} diff --git a/src/discord/commands/create_tag.rs b/src/discord/commands/create_tag.rs index e69de29..64f9993 100644 --- a/src/discord/commands/create_tag.rs +++ b/src/discord/commands/create_tag.rs @@ -0,0 +1,72 @@ +use mongodb::bson::doc; + +use serenity::{ + builder::{CreateApplicationCommand, CreateInteractionResponseData}, + model::{ + application::interaction::application_command::ApplicationCommandInteraction, + prelude::{ + command::CommandOptionType, interaction::application_command::CommandDataOptionValue, + }, + }, +}; + +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) + }) +} + +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"), + }; + } + } + + response +} diff --git a/src/discord/commands/delete_tag.rs b/src/discord/commands/delete_tag.rs new file mode 100644 index 0000000..63e8c6b --- /dev/null +++ b/src/discord/commands/delete_tag.rs @@ -0,0 +1,14 @@ +use serenity::{builder::CreateApplicationCommand, model::prelude::command::CommandOptionType}; + +pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { + command + .name("delete_tag") + .description("Delete a tag") + .create_option(|option| { + option + .name("tag") + .description("The tag to delete") + .kind(CommandOptionType::String) + .required(true) + }) +} diff --git a/src/discord/commands/list_tags.rs b/src/discord/commands/list_tags.rs new file mode 100644 index 0000000..67b1101 --- /dev/null +++ b/src/discord/commands/list_tags.rs @@ -0,0 +1,5 @@ +use serenity::builder::CreateApplicationCommand; + +pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { + command.name("list_tags").description("List your own tags") +} diff --git a/src/discord/commands/source_code.rs b/src/discord/commands/source_code.rs new file mode 100644 index 0000000..ce2e919 --- /dev/null +++ b/src/discord/commands/source_code.rs @@ -0,0 +1,17 @@ +use serenity::builder::{CreateApplicationCommand, CreateInteractionResponseData}; + +pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { + command + .name("source_code") + .description("Access to the bot source code") +} + +pub fn run<'a, 'b>( + response: &'a mut CreateInteractionResponseData<'b>, +) -> &'a mut CreateInteractionResponseData<'b> { + response.embed(|embed| { + embed + .title("Yorokobot repository") + .description("https://sr.ht/~victormignot/yorokobot/") + }) +} diff --git a/src/discord/event_handler.rs b/src/discord/event_handler.rs index f6bdede..f99c220 100644 --- a/src/discord/event_handler.rs +++ b/src/discord/event_handler.rs @@ -1,87 +1,69 @@ +use crate::database::Client as DatabaseClient; use serenity::{ async_trait, model::gateway::Ready, model::{ - application::command::{Command, CommandOptionType}, - prelude::ResumedEvent, + application::command::Command, + prelude::{ + interaction::{Interaction, InteractionResponseType}, + ResumedEvent, + }, }, prelude::{Context, EventHandler}, }; -use crate::database::Client as DatabaseClient; +use super::commands::*; const MAX_ARGS_NUMBER: u32 = 25; -pub struct Handler<'a> { - pub database: &'a DatabaseClient, +pub struct Handler { + pub database: DatabaseClient, } #[async_trait] -impl EventHandler for Handler<'_> { +impl EventHandler for Handler { async fn ready(&self, ctx: Context, ready: Ready) { println!("Successfully connected as {}", ready.user.name); - match Command::create_global_application_command(&ctx.http, |command| { - 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) + 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 create_tag command"), - Err(e) => println!("Failed to register create_tag command : {e}"), - }; - - match Command::create_global_application_command(&ctx.http, |command| { - let command = command - .name("bulk_create_tag") - .description("Add multiples tags"); - - for i in 0..MAX_ARGS_NUMBER { - command.create_option(|option| { - option - .name(format!("tag{}", i + 1)) - .description("A new tag to add") - .kind(CommandOptionType::String) - .required(i == 0) - }); - } - - command - }) - .await - { - Ok(_) => println!("Successfully registered bulk_create_tag command"), - Err(e) => println!("Failed to register bulk_create_tag command: {e}"), - }; - - match Command::create_global_application_command(&ctx.http, |command| { - command - .name("delete_tag") - .description("Delete a tag") - .create_option(|option| { - option - .name("tag") - .description("The tag to delete") - .kind(CommandOptionType::String) - .required(true) - }) - }) - .await - { - Ok(_) => println!("Successfully registered delete_tag command"), - Err(e) => println!("Failed to register delete_tag command: {e}"), + Ok(_) => println!("Successfully registered application commands"), + Err(e) => println!("Failed to register application commands: {e}"), }; } async fn resume(&self, _: Context, _: ResumedEvent) { println!("Successfully reconnected.") } + + 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}"); + } + } + } }