diff --git a/README.md b/README.md index f97a23c..f15ed8a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ Main lib used : [serenity-rs/serenity](https://github.com/serenity-rs/serenity) ## Feature expected -- ImageWareHouse (like TestDiscord) +- [DONE] ImageWareHouse (like TestDiscord) +- ImageWareHouse V2 + - Mise en place d'autre source de gif OPT - SoundBoard (like UnlabeledBot) - Notification (read an event topic and send the message to the expected chan) - Some Administration command @@ -20,13 +22,12 @@ Main lib used : [serenity-rs/serenity](https://github.com/serenity-rs/serenity) - Have a web interface to manage the bot (like a dashboard) - List of server - Monitor the bot -- Integrate with the Opentelemetry project + - Authentification Discord + - Detecter controle +- [WIP] Integrate with the Opentelemetry project - The bot has to be able to be deployed on a k8s cluster - The bot has to be OPT-IN (the user has to enable the bot on his server with a command) - -## Important to do - -- Migrate to Trace +- Mise en place de metric OpenTelemetry ## previous project diff --git a/src/botv2/cmd/concour/mod.rs b/src/botv2/cmd/concour/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/botv2/cmd/help.rs b/src/botv2/cmd/help.rs index b060ea0..f12ae3c 100644 --- a/src/botv2/cmd/help.rs +++ b/src/botv2/cmd/help.rs @@ -1,10 +1,10 @@ +use crate::botv2::init::{Context, Error}; use poise::samples::HelpConfiguration; use tracing::instrument; -use crate::botv2::init::{Context,Error}; /// Show help message #[poise::command(prefix_command, track_edits, category = "Utility")] -#[instrument(skip(ctx),level="info")] +#[instrument(skip(ctx), level = "info")] pub async fn help( ctx: Context<'_>, #[description = "Command to get help for"] @@ -22,7 +22,7 @@ pub async fn help( }; } let extra_text_at_bottom = "\ -Provided by Batleforc with love ❤️ and too much coffee ☕"; +Provided by Batleforc with ❤️ and too much ☕"; let config = HelpConfiguration { show_subcommands: true, @@ -34,4 +34,4 @@ Provided by Batleforc with love ❤️ and too much coffee ☕"; }; poise::builtins::help(ctx, command.as_deref(), config).await?; Ok(()) -} \ No newline at end of file +} diff --git a/src/botv2/cmd/meme/answer.rs b/src/botv2/cmd/meme/answer.rs index 069d6b8..a6f3de7 100644 --- a/src/botv2/cmd/meme/answer.rs +++ b/src/botv2/cmd/meme/answer.rs @@ -5,6 +5,7 @@ use crate::botv2::{ use poise::CreateReply; use tracing::instrument; +/// Answer keyword with a meme if it match a registered keyword #[instrument(skip(ctx), level = "info", fields(channel = ctx.channel_id().get(), guild = ?ctx.guild_id().unwrap().get()))] #[poise::command(slash_command, prefix_command, category = "meme")] pub async fn answer( diff --git a/src/botv2/cmd/mod.rs b/src/botv2/cmd/mod.rs index 6ac1ed8..d6d49c4 100644 --- a/src/botv2/cmd/mod.rs +++ b/src/botv2/cmd/mod.rs @@ -1,3 +1,5 @@ -pub mod ping; +pub mod concour; pub mod help; -pub mod meme; \ No newline at end of file +pub mod meme; +pub mod ping; +pub mod server_config; diff --git a/src/botv2/cmd/server_config/feature.rs b/src/botv2/cmd/server_config/feature.rs new file mode 100644 index 0000000..6a594a0 --- /dev/null +++ b/src/botv2/cmd/server_config/feature.rs @@ -0,0 +1,81 @@ +use crate::botv2::{ + domain::server_config::change_enable_server::change_enable_server, + init::{Context, Error}, +}; +use poise::{ + serenity_prelude::{model::colour, CreateEmbed, CreateEmbedFooter}, + CreateReply, +}; +use tracing::instrument; + +/// Enable/Disable feature +#[instrument(skip(ctx), level = "info", fields(channel = ctx.channel_id().get(), guild = ?ctx.guild_id().unwrap().get()))] +#[poise::command( + slash_command, + prefix_command, + category = "server_config", + guild_only = true +)] +pub async fn feature( + ctx: Context<'_>, + #[description = "Enable/Disable the auto_meme feature"] auto_meme: Option, + #[description = "Enable/Disable the concour feature"] concour: Option, +) -> Result<(), Error> { + let guild = match ctx.guild_id() { + Some(guild) => guild, + None => return Ok(()), + }; + let entity_name = ctx.data().entity_name.clone(); + let footer = CreateEmbedFooter::new(entity_name.clone()); + let answer_auto_meme = match auto_meme { + Some(enable) => match change_enable_server(guild.get(), enable).await { + Ok(_) => Some( + CreateEmbed::new() + .title(format!("Auto meme feature initialized: {}", enable)) + .color(colour::Color::DARK_GREEN), + ), + Err(_) => Some( + CreateEmbed::new() + .title("Error initializing auto meme feature") + .color(colour::Color::RED), + ), + }, + None => None, + }; + let answer_enable_concour = match concour { + Some(enable) => match change_enable_server(guild.get(), enable).await { + Ok(_) => Some( + CreateEmbed::new() + .title(format!("Concour feature initialized: {}", enable)) + .color(colour::Color::DARK_GREEN), + ), + Err(_) => Some( + CreateEmbed::new() + .title("Error initializing concour feature") + .color(colour::Color::RED), + ), + }, + None => None, + }; + + let mut builder = CreateReply::default().ephemeral(true); + if answer_auto_meme.is_none() && answer_enable_concour.is_none() { + builder = builder.embed( + CreateEmbed::new() + .title("No feature initialized") + .color(colour::Color::RED) + .footer(footer.clone()), + ); + } + if let Some(embed) = answer_auto_meme { + builder = builder.embed(embed.footer(footer.clone())); + } + if let Some(embed) = answer_enable_concour { + builder = builder.embed(embed.footer(footer)); + } + + if let Err(why) = ctx.send(builder).await { + tracing::error!("Error sending message: {:?}", why); + } + Ok(()) +} diff --git a/src/botv2/cmd/server_config/init_server.rs b/src/botv2/cmd/server_config/init_server.rs new file mode 100644 index 0000000..fc6f3cd --- /dev/null +++ b/src/botv2/cmd/server_config/init_server.rs @@ -0,0 +1,46 @@ +use crate::botv2::{ + domain::server_config::change_enable_server::change_enable_server, + init::{Context, Error}, +}; +use poise::{ + serenity_prelude::{model::colour, CreateEmbed, CreateEmbedFooter}, + CreateReply, +}; +use tracing::instrument; + +/// Initialize server config +#[instrument(skip(ctx), level = "info", fields(channel = ctx.channel_id().get(), guild = ?ctx.guild_id().unwrap().get()))] +#[poise::command( + slash_command, + prefix_command, + category = "server_config", + guild_only = true, + owners_only = true +)] +pub async fn init( + ctx: Context<'_>, + #[description = "Enable the bot to answer to the server"] enable: bool, +) -> Result<(), Error> { + let guild = match ctx.guild_id() { + Some(guild) => guild, + None => return Ok(()), + }; + ctx.author_member().await.unwrap(); + let entity_name = ctx.data().entity_name.clone(); + let footer = CreateEmbedFooter::new(entity_name.clone()); + let answer = match change_enable_server(guild.get(), enable).await { + Ok(_) => CreateEmbed::new() + .title("Server config initialized") + .color(colour::Color::DARK_GREEN), + Err(_) => CreateEmbed::new() + .title("Error initializing server config") + .color(colour::Color::RED), + }; + let builder = CreateReply::default() + .embed(answer.footer(footer)) + .ephemeral(true); + if let Err(why) = ctx.send(builder).await { + tracing::error!("Error sending message: {:?}", why); + } + Ok(()) +} diff --git a/src/botv2/cmd/server_config/mod.rs b/src/botv2/cmd/server_config/mod.rs new file mode 100644 index 0000000..554fc4c --- /dev/null +++ b/src/botv2/cmd/server_config/mod.rs @@ -0,0 +1,3 @@ +pub mod feature; +pub mod init_server; +pub mod server; diff --git a/src/botv2/cmd/server_config/server.rs b/src/botv2/cmd/server_config/server.rs new file mode 100644 index 0000000..49db84e --- /dev/null +++ b/src/botv2/cmd/server_config/server.rs @@ -0,0 +1,104 @@ +use crate::botv2::{ + cmd::server_config::{feature::feature, init_server::init}, + domain::server_config::get_server_config::get_server_config, + init::{Context, Error}, +}; +use poise::{ + serenity_prelude::{model::colour, CreateEmbed, CreateEmbedFooter, RoleId}, + CreateReply, +}; +use tracing::instrument; + +/// Show server config (alias of info) +#[instrument(skip(ctx), level = "info", fields(channel = ctx.channel_id().get(), guild = ?ctx.guild_id().unwrap().get()))] +#[poise::command( + slash_command, + prefix_command, + category = "server_config", + subcommands("init", "info", "feature"), + guild_only = true +)] +pub async fn server( + ctx: Context<'_>, + #[description = "Reponse cacher ?"] ephemeral: Option, +) -> Result<(), Error> { + server_info(ctx, ephemeral).await +} + +/// Show server config +#[instrument(skip(ctx), level = "info", fields(channel = ctx.channel_id().get(), guild = ?ctx.guild_id().unwrap().get()))] +#[poise::command( + slash_command, + prefix_command, + category = "server_config", + guild_only = true +)] +pub async fn info( + ctx: Context<'_>, + #[description = "Reponse cacher ?"] ephemeral: Option, +) -> Result<(), Error> { + server_info(ctx, ephemeral).await +} + +#[instrument(skip(ctx), level = "info")] +async fn server_info(ctx: Context<'_>, ephemeral: Option) -> Result<(), Error> { + let guild = match ctx.guild_id() { + Some(guild) => guild, + None => return Ok(()), + }; + let entity_name = ctx.data().entity_name.clone(); + let footer = CreateEmbedFooter::new(entity_name.clone()); + let answer = match get_server_config(guild.get()).await { + Ok(config) => match config { + Some(server_config) => { + let mut embed = CreateEmbed::new() + .title(format!( + "Server config found: {}", + ctx.guild().unwrap().name + )) + .field("Enabled", server_config.enable.to_string(), true) + .field( + "Feature Auto_meme", + server_config.auto_meme.to_string(), + true, + ) + .field( + "Feature Concour", + server_config.auto_concour.to_string(), + true, + ) + .color(colour::Color::DARK_GREEN); + + let role_list: Vec = server_config + .admin_role + .iter() + .map(|role_id| { + ctx.guild().unwrap().roles[&RoleId::new(*role_id)] + .name + .clone() + }) + .collect(); + if !role_list.is_empty() { + embed = embed.field("Admin role", role_list.join(", "), false); + } else { + embed = embed.field("Admin role", "No role found", false); + } + embed + } + None => CreateEmbed::new() + .title("Server config not found") + .color(colour::Color::RED), + }, + Err(_) => CreateEmbed::new() + .title("Error fetching server config") + .color(colour::Color::RED), + }; + let mut builder = CreateReply::default().embed(answer.footer(footer)); + if ephemeral.unwrap_or(true) { + builder = builder.ephemeral(true); + } + if let Err(why) = ctx.send(builder).await { + tracing::error!("Error sending message: {:?}", why); + } + Ok(()) +} diff --git a/src/botv2/domain/concour/check_if_allowed.rs b/src/botv2/domain/concour/check_if_allowed.rs new file mode 100644 index 0000000..c2e4d70 --- /dev/null +++ b/src/botv2/domain/concour/check_if_allowed.rs @@ -0,0 +1,57 @@ +use poise::serenity_prelude; +use serde::{Deserialize, Serialize}; +use tracing::instrument; + +use crate::{ + botv2::domain::server_config::{ + check_if_server_enable::check_if_server_enable, + check_if_server_enable_and_admin::check_if_server_enable_and_user_admin, + }, + db::server_config::ServerConfig, +}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum CheckIfConcourEnable { + UnknownError(String), +} + +// Check : +// - if the user is admin or owner of the server +// - if the server enabled the concour feature +// - if the user has a role allowed to handle concour + +/// Check if the concour is allowed on the server +#[instrument(level = "info")] +pub async fn check_if_concour_allowed( + server_id: u64, +) -> Result<(bool, Option), CheckIfConcourEnable> { + if let Ok((enable, server_config)) = check_if_server_enable(server_id).await { + if !enable { + return Ok((false, None)); + } + if let Some(server_config) = server_config { + return Ok((server_config.auto_concour, Some(server_config))); + } + } + Ok((false, None)) +} + +/// Check if the concour is allowed on the server and if the user has a role allowed to handle concour +#[instrument(level = "info", skip(http))] +pub async fn check_if_allowed( + server_id: u64, + user_id: u64, + http: serenity_prelude::Http, +) -> Result { + if let Ok((enable, server_config)) = + check_if_server_enable_and_user_admin(server_id, user_id, http).await + { + if !enable { + return Ok(false); + } + if let Some(server_config) = server_config { + return Ok(server_config.auto_concour); + } + } + Ok(false) +} diff --git a/src/botv2/domain/concour/list_concour.rs b/src/botv2/domain/concour/list_concour.rs new file mode 100644 index 0000000..87d8267 --- /dev/null +++ b/src/botv2/domain/concour/list_concour.rs @@ -0,0 +1,29 @@ +use serde::{Deserialize, Serialize}; +use tracing::{info, instrument}; + +use crate::db::concour::Concour; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum ListConcourError { + FindError(String), + UnknownError(String), +} + +#[instrument(level = "info")] +pub async fn list_concour(server_id: u64) -> Result, ListConcourError> { + let list_concour = match Concour::find_by_server_id(&server_id).await { + Ok(list_concour) => list_concour, + Err(err) => { + tracing::error!(error = err.to_string(), "Error finding concour"); + return Err(ListConcourError::UnknownError( + "Error finding concour".to_string(), + )); + } + }; + if list_concour.is_empty() { + info!("No concour found"); + return Err(ListConcourError::FindError("No concour found".to_string())); + } + + Ok(list_concour) +} diff --git a/src/botv2/domain/concour/mod.rs b/src/botv2/domain/concour/mod.rs new file mode 100644 index 0000000..12a7fb9 --- /dev/null +++ b/src/botv2/domain/concour/mod.rs @@ -0,0 +1,2 @@ +pub mod check_if_allowed; +pub mod list_concour; diff --git a/src/botv2/domain/concour/readme.md b/src/botv2/domain/concour/readme.md new file mode 100644 index 0000000..3b30fd3 --- /dev/null +++ b/src/botv2/domain/concour/readme.md @@ -0,0 +1,53 @@ +# Concour + +## Kind Popularity + +- Titre +- Théme +- Date debut/fin +- Récompense + - Manuelle + - Automatique + - Role + - Message personalisé contenant ce que tu veux + +### Déclarer un concour de popularité + +1. Creer un channel +2. Préparer la récompense +3. Creer et configuré le concours de popularité dans le channel cible + 1. Creer : /concours-create kind:(string) title:(string) description:(string) fin:(date) (retourne l'id du concours) + 2. Changer les info : /concours-edit id:(object-id) banniere:(opt,string-url) title:(opt,string) description:(opt,string) fin:(date) + 3. Annoncer le concours : /concours-promo id:(object-id) + 4. Definir la récompense : /concours-set-recompense id:(object-id) kind:(string) content:(string,opt) + 5. Definir le channel : /concours-set-channel id:(object-id) channel-id:(u64) + 6. Cloturer le concours : /concours-end id:(object-id) fin:(date,opt) + 7. Annoncer le vainqueur : /concours-win id:(object-id) (si non finis, cloture le concours immédiatement) + +### Sélectionner un vainqueur ? + +Toute personne qui aura le plus d'émoji sur un message contenant une image + +## Kind Automated Popularity + +- Titre => String +- Description => String +- Date début => Date +- Periode (1 Semaine ?) => Duration +- CurrentWinner => Option +- RoleRécompense => u64 +- KeyWordList => Vec +- Banniére => Option +- CurrentKeyWord => String +- Status => Enum +- HistoriqueWinner => HashMap> + +### Command + +- Creation /concours-create kind:(string) title:(string) description:(string) periode:(date) +- Start /concours-start +- List concours /concours-list + +## Kind Check-in + +Bot de concours avec participation reaction emoji \ No newline at end of file diff --git a/src/botv2/domain/mod.rs b/src/botv2/domain/mod.rs index 6b0b972..d0de60d 100644 --- a/src/botv2/domain/mod.rs +++ b/src/botv2/domain/mod.rs @@ -1 +1,3 @@ -pub mod meme; \ No newline at end of file +pub mod concour; +pub mod meme; +pub mod server_config; diff --git a/src/botv2/domain/server_config/change_enable_server.rs b/src/botv2/domain/server_config/change_enable_server.rs new file mode 100644 index 0000000..a19b769 --- /dev/null +++ b/src/botv2/domain/server_config/change_enable_server.rs @@ -0,0 +1,60 @@ +use serde::{Deserialize, Serialize}; +use tracing::instrument; + +use crate::db::server_config::ServerConfig; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum ChangeEnableServerError { + UnknownError(String), +} + +#[instrument(level = "info")] +pub async fn change_enable_server( + server_id: u64, + enable: bool, +) -> Result<(), ChangeEnableServerError> { + let server_config = + match crate::db::server_config::ServerConfig::find_by_server_id(&server_id).await { + Ok(server_config) => server_config, + Err(err) => { + tracing::error!(error = err.to_string(), "Error finding server config"); + return Err(ChangeEnableServerError::UnknownError( + "Error finding server config".to_string(), + )); + } + }; + match server_config { + None => { + tracing::info!("Server config not found, creating one"); + let mut server_config = ServerConfig::new(server_id).unwrap(); + server_config.enable = enable; + match server_config.create().await { + Ok(_) => Ok(()), + Err(err) => { + tracing::error!(error = err.to_string(), "Error creating server config"); + return Err(ChangeEnableServerError::UnknownError( + "Error creating server config".to_string(), + )); + } + } + } + Some(mut server_config) => { + if server_config.enable == enable { + tracing::info!("Server config already enabled/disabled"); + return Ok(()); + } else { + tracing::info!("Updating server config"); + server_config.enable = enable; + match server_config.update().await { + Ok(_) => Ok(()), + Err(err) => { + tracing::error!(error = err.to_string(), "Error updating server config"); + return Err(ChangeEnableServerError::UnknownError( + "Error updating server config".to_string(), + )); + } + } + } + } + } +} diff --git a/src/botv2/domain/server_config/check_if_server_enable.rs b/src/botv2/domain/server_config/check_if_server_enable.rs new file mode 100644 index 0000000..8e16eaa --- /dev/null +++ b/src/botv2/domain/server_config/check_if_server_enable.rs @@ -0,0 +1,32 @@ +use serde::{Deserialize, Serialize}; +use tracing::instrument; + +use crate::db::server_config::ServerConfig; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum CheckIfServerEnableError { + UnknownError(String), +} + +#[instrument(level = "info")] +pub async fn check_if_server_enable( + server_id: u64, +) -> Result<(bool, Option), CheckIfServerEnableError> { + let server_config = + match crate::db::server_config::ServerConfig::find_by_server_id(&server_id).await { + Ok(server_config) => server_config, + Err(err) => { + tracing::error!(error = err.to_string(), "Error finding server config"); + return Err(CheckIfServerEnableError::UnknownError( + "Error finding server config".to_string(), + )); + } + }; + match server_config { + None => { + tracing::info!("Server config not found"); + return Ok((false, None)); + } + Some(server_config) => Ok((server_config.enable, Some(server_config))), + } +} diff --git a/src/botv2/domain/server_config/check_if_server_enable_and_admin.rs b/src/botv2/domain/server_config/check_if_server_enable_and_admin.rs new file mode 100644 index 0000000..81f0d10 --- /dev/null +++ b/src/botv2/domain/server_config/check_if_server_enable_and_admin.rs @@ -0,0 +1,49 @@ +use super::check_if_server_enable::{check_if_server_enable, CheckIfServerEnableError}; +use crate::db::server_config::ServerConfig; +use poise::serenity_prelude::{self, RoleId, UserId}; +use tracing::instrument; + +#[instrument(level = "info", skip(http))] +pub async fn check_if_server_enable_and_user_admin( + server_id: u64, + user_id: u64, + http: serenity_prelude::Http, +) -> Result<(bool, Option), CheckIfServerEnableError> { + let server_config = match check_if_server_enable(server_id).await { + Ok((status, config)) => { + if !status { + return Ok((false, None)); + } + config.unwrap() + } + Err(err) => return Err(err), + }; + let guild = match http.get_guild(server_id.into()).await { + Ok(guild) => guild, + Err(err) => { + tracing::error!(error = err.to_string(), "Error getting guild"); + return Err(CheckIfServerEnableError::UnknownError( + "Error getting guild".to_string(), + )); + } + }; + if guild.owner_id.get() == user_id { + return Ok((true, Some(server_config))); + } + match guild.member(http, UserId::new(user_id)).await { + Ok(member) => Ok(( + server_config + .clone() + .admin_role + .into_iter() + .any(|role_id| member.roles.contains(&RoleId::new(role_id))), + Some(server_config), + )), + Err(err) => { + tracing::error!(error = err.to_string(), "Error getting member"); + return Err(CheckIfServerEnableError::UnknownError( + "Error getting member".to_string(), + )); + } + } +} diff --git a/src/botv2/domain/server_config/enable_feature_auto_meme.rs b/src/botv2/domain/server_config/enable_feature_auto_meme.rs new file mode 100644 index 0000000..9a954c5 --- /dev/null +++ b/src/botv2/domain/server_config/enable_feature_auto_meme.rs @@ -0,0 +1,58 @@ +use serde::{Deserialize, Serialize}; +use tracing::instrument; + +use crate::db::server_config::ServerConfig; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum EnableFeatureMemeError { + UnknownError(String), +} + +#[instrument(level = "info")] +pub async fn enable_feature_meme( + server_id: u64, + enable: bool, +) -> Result<(), EnableFeatureMemeError> { + let server_config = + match crate::db::server_config::ServerConfig::find_by_server_id(&server_id).await { + Ok(server_config) => server_config, + Err(err) => { + tracing::error!(error = err.to_string(), "Error finding server config"); + return Err(EnableFeatureMemeError::UnknownError( + "Error finding server config".to_string(), + )); + } + }; + match server_config { + None => { + tracing::info!("Server config not found, creating it"); + let mut server_config = ServerConfig::new(server_id).unwrap(); + server_config.auto_meme = enable; + match server_config.create().await { + Ok(_) => Ok(()), + Err(err) => { + tracing::error!(error = err.to_string(), "Error creating server config"); + return Err(EnableFeatureMemeError::UnknownError( + "Error creating server config".to_string(), + )); + } + } + } + Some(mut server_config) => { + if server_config.auto_meme == enable { + return Ok(()); + } else { + server_config.auto_meme = enable; + match server_config.update().await { + Ok(_) => Ok(()), + Err(err) => { + tracing::error!(error = err.to_string(), "Error updating server config"); + return Err(EnableFeatureMemeError::UnknownError( + "Error updating server config".to_string(), + )); + } + } + } + } + } +} diff --git a/src/botv2/domain/server_config/enable_feature_concour.rs b/src/botv2/domain/server_config/enable_feature_concour.rs new file mode 100644 index 0000000..44dba54 --- /dev/null +++ b/src/botv2/domain/server_config/enable_feature_concour.rs @@ -0,0 +1,58 @@ +use serde::{Deserialize, Serialize}; +use tracing::instrument; + +use crate::db::server_config::ServerConfig; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum EnableFeatureConcourError { + UnknownError(String), +} + +#[instrument(level = "info")] +pub async fn enable_feature_concour( + server_id: u64, + enable: bool, +) -> Result<(), EnableFeatureConcourError> { + let server_config = + match crate::db::server_config::ServerConfig::find_by_server_id(&server_id).await { + Ok(server_config) => server_config, + Err(err) => { + tracing::error!(error = err.to_string(), "Error finding server config"); + return Err(EnableFeatureConcourError::UnknownError( + "Error finding server config".to_string(), + )); + } + }; + match server_config { + None => { + tracing::info!("Server config not found, creating it"); + let mut server_config = ServerConfig::new(server_id).unwrap(); + server_config.auto_concour = enable; + match server_config.create().await { + Ok(_) => Ok(()), + Err(err) => { + tracing::error!(error = err.to_string(), "Error creating server config"); + return Err(EnableFeatureConcourError::UnknownError( + "Error creating server config".to_string(), + )); + } + } + } + Some(mut server_config) => { + if server_config.auto_concour == enable { + return Ok(()); + } else { + server_config.auto_concour = enable; + match server_config.update().await { + Ok(_) => Ok(()), + Err(err) => { + tracing::error!(error = err.to_string(), "Error updating server config"); + return Err(EnableFeatureConcourError::UnknownError( + "Error updating server config".to_string(), + )); + } + } + } + } + } +} diff --git a/src/botv2/domain/server_config/get_server_config.rs b/src/botv2/domain/server_config/get_server_config.rs new file mode 100644 index 0000000..cdb5d9b --- /dev/null +++ b/src/botv2/domain/server_config/get_server_config.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; +use tracing::instrument; + +use crate::db::server_config::ServerConfig; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum GetServerConfigError { + UnknownError(String), +} + +#[instrument(level = "info")] +pub async fn get_server_config( + server_id: u64, +) -> Result, GetServerConfigError> { + let server_config = + match crate::db::server_config::ServerConfig::find_by_server_id(&server_id).await { + Ok(server_config) => server_config, + Err(err) => { + tracing::error!(error = err.to_string(), "Error finding server config"); + return Err(GetServerConfigError::UnknownError( + "Error finding server config".to_string(), + )); + } + }; + Ok(server_config) +} diff --git a/src/botv2/domain/server_config/mod.rs b/src/botv2/domain/server_config/mod.rs new file mode 100644 index 0000000..3c072aa --- /dev/null +++ b/src/botv2/domain/server_config/mod.rs @@ -0,0 +1,6 @@ +pub mod change_enable_server; +pub mod check_if_server_enable; +pub mod check_if_server_enable_and_admin; +pub mod enable_feature_auto_meme; +pub mod enable_feature_concour; +pub mod get_server_config; diff --git a/src/botv2/init.rs b/src/botv2/init.rs index 617cb79..594f209 100644 --- a/src/botv2/init.rs +++ b/src/botv2/init.rs @@ -1,4 +1,5 @@ use crate::botv2::cmd::meme::{answer::answer, enable::enable, list::list}; +use crate::botv2::cmd::server_config::server::server; use crate::botv2::cmd::{help::help, ping::ping}; use crate::config::Config; use crate::{botv2::handler::event_handler, img::config_file::ConfigFile}; @@ -58,7 +59,7 @@ pub async fn start_bot(config: Config, rx: oneshot::Receiver<()>) -> Arc { let prefix = config.prefix.clone(); let framework = poise::Framework::builder() .options(poise::FrameworkOptions { - commands: vec![age(), ping(), help(), list(), enable(), answer()], + commands: vec![age(), ping(), help(), list(), enable(), answer(), server()], prefix_options: poise::PrefixFrameworkOptions { prefix: Some(prefix.into()), ..Default::default() diff --git a/src/db/concour.rs b/src/db/concour.rs new file mode 100644 index 0000000..c899f11 --- /dev/null +++ b/src/db/concour.rs @@ -0,0 +1,121 @@ +use super::init::DB; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fmt; +use surrealdb::opt::Resource; + +const CONCOUR: &str = "concour"; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub enum ConcourStatus { + Created, + Paused, + OnGoing, + Finished, +} + +impl fmt::Display for ConcourStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Concour { + pub server_id: u64, + pub enable: bool, + pub channel_id: u64, + pub title: String, + pub description: String, + pub start_date: chrono::DateTime, + pub periode: time::Duration, + pub role_récompense: u64, + pub keywords: Vec, + pub banner: Option, + pub index_keyword: u64, + pub status: ConcourStatus, + pub winner: HashMap)>, +} + +impl Concour { + pub fn new(server_id: u64, channel_id: u64, enable: bool) -> Result { + Ok(Self { + server_id, + enable, + channel_id, + title: String::new(), + description: String::new(), + start_date: chrono::Utc::now(), + periode: time::Duration::days(0), + role_récompense: 0, + keywords: Vec::new(), + banner: None, + index_keyword: 0, + status: ConcourStatus::Created, + winner: HashMap::new(), + }) + } + pub async fn create(&self) -> Result<(), surrealdb::Error> { + match DB.create(Resource::from(CONCOUR)).content(&self).await { + Ok(_) => {} + Err(e) => { + return Err(e); + } + }; + Ok(()) + } + pub async fn update(&self) -> Result<(), surrealdb::Error> { + let sql = format!( + "UPDATE {} SET enable = {}, title = '{}', description = '{}', start_date = '{}', periode = '{}', role_récompense = {}, keywords = '{:?}', banner = '{:?}', index_keyword = {}, status = '{}', winner = '{:?}' WHERE server_id = {} and channel_id = {}", + CONCOUR, self.enable, self.title, self.description, self.start_date, self.periode, self.role_récompense, self.keywords, self.banner, self.index_keyword, self.status, self.winner, self.server_id, self.channel_id + ); + match DB.query(sql).await { + Ok(res) => { + println!("{:?}", res); + } + Err(e) => { + return Err(e); + } + }; + Ok(()) + } + pub async fn find_by_server_id_channel_id( + server_id: &u64, + channel_id: &u64, + ) -> Result, surrealdb::Error> { + let sql = format!( + "SELECT * FROM {} WHERE server_id = {} AND channel_id = {}", + CONCOUR, server_id, channel_id + ); + let mut results = match DB.query(&sql).await { + Ok(results) => results, + Err(e) => { + return Err(e); + } + }; + let concour: Concour = match results.take(0) { + Ok(Some(concour)) => concour, + Ok(None) => { + return Ok(None); + } + Err(e) => { + return Err(e); + } + }; + Ok(Some(concour)) + } + pub async fn find_by_server_id(server_id: &u64) -> Result, surrealdb::Error> { + let sql = format!("SELECT * FROM {} WHERE server_id = {}", CONCOUR, server_id); + let mut results = match DB.query(&sql).await { + Ok(results) => results, + Err(e) => { + return Err(e); + } + }; + let mut concours = Vec::new(); + while let Ok(Some(concour)) = results.take(0) { + concours.push(concour); + } + Ok(concours) + } +} diff --git a/src/db/mod.rs b/src/db/mod.rs index 9221b36..0b79007 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,2 +1,4 @@ +pub mod concour; +pub mod init; +pub mod server_config; pub mod user_image; -pub mod init; \ No newline at end of file diff --git a/src/db/server_config.rs b/src/db/server_config.rs new file mode 100644 index 0000000..4ca0cb1 --- /dev/null +++ b/src/db/server_config.rs @@ -0,0 +1,89 @@ +use super::init::DB; +use serde::{Deserialize, Serialize}; +use std::fmt; +use surrealdb::opt::Resource; + +const SERVER_CONFIG: &str = "server_config"; +const fn default_false() -> bool { + false +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ServerConfig { + #[serde(default = "default_false")] + pub enable: bool, + pub server_id: u64, + #[serde(default = "default_false")] + pub auto_meme: bool, + #[serde(default = "default_false")] + pub auto_concour: bool, + pub admin_role: Vec, +} + +impl fmt::Display for ServerConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl ServerConfig { + pub fn new(server_id: u64) -> Result { + Ok(Self { + enable: false, + server_id, + auto_meme: false, + auto_concour: false, + admin_role: Vec::new(), + }) + } + pub async fn create(&self) -> Result<(), surrealdb::Error> { + match DB + .create(Resource::from(SERVER_CONFIG)) + .content(&self) + .await + { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + } + pub async fn update(&self) -> Result<(), surrealdb::Error> { + let sql = format!( + "UPDATE {} SET enable = {}, auto_meme = {}, auto_concour = {}, admin_role = '{:?}' WHERE server_id = {}", + SERVER_CONFIG, + self.enable, + self.auto_meme, + self.auto_concour, + self.admin_role, + self.server_id + ); + match DB.query(&sql).await { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + } + pub async fn find_by_server_id( + server_id: &u64, + ) -> Result, surrealdb::Error> { + let sql = format!( + "SELECT * FROM {} WHERE server_id = {}", + SERVER_CONFIG, server_id + ); + let mut results = match DB.query(&sql).await { + Ok(results) => results, + Err(e) => { + return Err(e); + } + }; + let server_config: ServerConfig = match results.take(0) { + Ok(Some(server_config)) => server_config, + Ok(None) => { + return Ok(None); + } + Err(e) => { + return Err(e); + } + }; + + Ok(Some(server_config)) + } +}