diff --git a/src/api/apidocs.rs b/src/api/apidocs.rs index c8c7c19..ccae828 100644 --- a/src/api/apidocs.rs +++ b/src/api/apidocs.rs @@ -1,6 +1,7 @@ use utoipa::OpenApi; use crate::api::bot::info; +use crate::db::{concour, server_config, user_image}; #[derive(OpenApi)] #[openapi( @@ -18,6 +19,10 @@ use crate::api::bot::info; schemas( info::Info, info::InfoGuild, + server_config::ServerConfig, + user_image::User, + concour::Concour, + concour::ConcourStatus, ) ), paths( diff --git a/src/botv2/cmd/meme/answer.rs b/src/botv2/cmd/meme/answer.rs index a6f3de7..e269db7 100644 --- a/src/botv2/cmd/meme/answer.rs +++ b/src/botv2/cmd/meme/answer.rs @@ -27,16 +27,20 @@ pub async fn answer( if let Ok(answer) = answer { match answer { AnswerResult::Embed(embeds, attachments) => { - let mut reply = CreateReply::default(); - reply.embeds = embeds; - reply.attachments = attachments; + let reply = CreateReply { + embeds, + attachments, + ..Default::default() + }; if let Err(why) = ctx.send(reply).await { tracing::error!("Error sending message: {:?}", why); } } AnswerResult::Animated(attachments) => { - let mut reply = CreateReply::default(); - reply.attachments = attachments; + let reply = CreateReply { + attachments, + ..Default::default() + }; if let Err(why) = ctx.send(reply).await { tracing::error!("Error sending message: {:?}", why); } diff --git a/src/botv2/cmd/meme/list.rs b/src/botv2/cmd/meme/list.rs index 4e2a5c6..96119e9 100644 --- a/src/botv2/cmd/meme/list.rs +++ b/src/botv2/cmd/meme/list.rs @@ -30,13 +30,13 @@ pub async fn list(ctx: Context<'_>) -> Result<(), Error> { .fields(chunks.to_vec()) .footer(footer) }); - let mut reply = CreateReply::default(); - reply.embeds = embed_vec.collect(); + let reply = CreateReply { + embeds: embed_vec.collect(), + ..Default::default() + }; if let Err(why) = ctx.send(reply).await { tracing::error!("Error sending message: {:?}", why); } Ok(()) } - -// https://github.com/serenity-rs/poise/blob/current/examples/fluent_localization/main.rs diff --git a/src/botv2/cmd/server_config/admin.rs b/src/botv2/cmd/server_config/admin.rs new file mode 100644 index 0000000..01b47f6 --- /dev/null +++ b/src/botv2/cmd/server_config/admin.rs @@ -0,0 +1,88 @@ +use crate::botv2::{ + domain::server_config::{ + admin_role::admin_role, + check_if_server_enable_and_admin::check_if_server_enable_and_user_admin, + }, + init::{Context, Error}, +}; +use poise::{ + serenity_prelude::{model::colour, CreateEmbed, CreateEmbedFooter, Role}, + CreateReply, +}; +use tracing::instrument; + +/// add/remove admin role +#[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 admin( + ctx: Context<'_>, + #[description = "Add admin role"] add: Option, + #[description = "Remove admin role"] remove: 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()); + match check_if_server_enable_and_user_admin(guild.get(), ctx.author().id.get(), ctx.http()) + .await + { + Ok((ok, _)) => { + if !ok { + let embed = CreateEmbed::new() + .title("You are not an admin") + .color(colour::Color::RED) + .footer(footer); + if let Err(why) = ctx + .send(CreateReply::default().embed(embed).ephemeral(true)) + .await + { + tracing::error!("Error sending message: {:?}", why); + } + return Ok(()); + } + } + Err(_) => { + let embed = CreateEmbed::new() + .title("You are not an admin") + .color(colour::Color::RED) + .footer(footer); + if let Err(why) = ctx + .send(CreateReply::default().embed(embed).ephemeral(true)) + .await + { + tracing::error!("Error sending message: {:?}", why); + } + return Ok(()); + } + }; + let role_add = match add { + Some(role) => Some(role.id.get()), + None => None, + }; + let role_remove = match remove { + Some(role) => Some(role.id.get()), + None => None, + }; + let mut output = match admin_role(guild.get(), role_add, role_remove).await { + Ok(_) => CreateEmbed::new() + .title("Success handling admin role") + .color(colour::Color::DARK_GREEN), + Err(_) => CreateEmbed::new() + .title("Error handling admin role") + .color(colour::Color::RED), + }; + output = output.footer(footer); + let mut builder = CreateReply::default().ephemeral(true); + builder = builder.embed(output); + if let Err(why) = ctx.send(builder).await { + tracing::error!("Error sending message: {:?}", why); + } + Ok(()) +} diff --git a/src/botv2/cmd/server_config/feature.rs b/src/botv2/cmd/server_config/feature.rs index 963cea9..381acb8 100644 --- a/src/botv2/cmd/server_config/feature.rs +++ b/src/botv2/cmd/server_config/feature.rs @@ -1,7 +1,8 @@ use crate::botv2::{ domain::server_config::{ - change_enable_server::change_enable_server, check_if_server_enable_and_admin::check_if_server_enable_and_user_admin, + enable_feature_auto_meme::enable_feature_meme, + enable_feature_concour::enable_feature_concour, }, init::{Context, Error}, }; @@ -63,7 +64,7 @@ pub async fn feature( } }; let answer_auto_meme = match auto_meme { - Some(enable) => match change_enable_server(guild.get(), enable).await { + Some(enable) => match enable_feature_meme(guild.get(), enable).await { Ok(_) => Some( CreateEmbed::new() .title(format!("Auto meme feature initialized: {}", enable)) @@ -78,7 +79,7 @@ pub async fn feature( None => None, }; let answer_enable_concour = match concour { - Some(enable) => match change_enable_server(guild.get(), enable).await { + Some(enable) => match enable_feature_concour(guild.get(), enable).await { Ok(_) => Some( CreateEmbed::new() .title(format!("Concour feature initialized: {}", enable)) diff --git a/src/botv2/cmd/server_config/mod.rs b/src/botv2/cmd/server_config/mod.rs index 554fc4c..90f981d 100644 --- a/src/botv2/cmd/server_config/mod.rs +++ b/src/botv2/cmd/server_config/mod.rs @@ -1,3 +1,4 @@ +pub mod admin; 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 index 3e6313c..c53e0bc 100644 --- a/src/botv2/cmd/server_config/server.rs +++ b/src/botv2/cmd/server_config/server.rs @@ -1,5 +1,5 @@ use crate::botv2::{ - cmd::server_config::{feature::feature, init_server::init}, + cmd::server_config::{admin::admin, feature::feature, init_server::init}, domain::server_config::{ check_if_server_enable_and_admin::check_if_server_enable_and_user_admin, get_server_config::get_server_config, @@ -18,7 +18,7 @@ use tracing::instrument; slash_command, prefix_command, category = "server_config", - subcommands("init", "info", "feature"), + subcommands("init", "info", "feature", "admin"), guild_only = true )] pub async fn server( diff --git a/src/botv2/domain/concour/create_concour.rs b/src/botv2/domain/concour/create_concour.rs new file mode 100644 index 0000000..bd2c94d --- /dev/null +++ b/src/botv2/domain/concour/create_concour.rs @@ -0,0 +1,53 @@ +use serde::{Deserialize, Serialize}; +use tracing::{info, instrument}; + +use crate::db::concour::Concour; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum CreateConcourError { + AlreadyExist, + FindError(String), + UnknownError(String), +} + +#[instrument(level = "info")] +pub async fn create_concour( + server_id: u64, + channel_id: u64, + title: String, + description: String, +) -> Result, CreateConcourError> { + let concour = match Concour::find_by_server_id_channel_id(&server_id, &channel_id).await { + Ok(list_concour) => list_concour, + Err(err) => { + tracing::error!(error = err.to_string(), "Error finding concour"); + return Err(CreateConcourError::UnknownError( + "Error finding concour".to_string(), + )); + } + }; + + if concour.is_some() { + info!("Concour already exist"); + return Err(CreateConcourError::AlreadyExist); + } + + let concour = Concour { + server_id, + channel_id, + title, + description, + ..Default::default() + }; + + match concour.create().await { + Ok(_) => {} + Err(err) => { + tracing::error!(error = err.to_string(), "Error creating concour"); + return Err(CreateConcourError::UnknownError( + "Error creating concour".to_string(), + )); + } + } + Ok(Some(concour)) +} diff --git a/src/botv2/domain/concour/get_channel_concour.rs b/src/botv2/domain/concour/get_channel_concour.rs new file mode 100644 index 0000000..d32b0ff --- /dev/null +++ b/src/botv2/domain/concour/get_channel_concour.rs @@ -0,0 +1,28 @@ +use serde::{Deserialize, Serialize}; +use tracing::instrument; + +use crate::db::concour::Concour; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum GetChannelConcourError { + FindError(String), + UnknownError(String), +} + +#[instrument(level = "info")] +pub async fn get_channel_concour( + server_id: u64, + channel_id: u64, +) -> Result, GetChannelConcourError> { + let concour = match Concour::find_by_server_id_channel_id(&server_id, &channel_id).await { + Ok(list_concour) => list_concour, + Err(err) => { + tracing::error!(error = err.to_string(), "Error finding concour"); + return Err(GetChannelConcourError::UnknownError( + "Error finding concour".to_string(), + )); + } + }; + + Ok(concour) +} diff --git a/src/botv2/domain/concour/mod.rs b/src/botv2/domain/concour/mod.rs index 12a7fb9..d7fbbe9 100644 --- a/src/botv2/domain/concour/mod.rs +++ b/src/botv2/domain/concour/mod.rs @@ -1,2 +1,4 @@ pub mod check_if_allowed; +pub mod create_concour; +pub mod get_channel_concour; pub mod list_concour; diff --git a/src/botv2/domain/server_config/admin_role.rs b/src/botv2/domain/server_config/admin_role.rs new file mode 100644 index 0000000..ac1023a --- /dev/null +++ b/src/botv2/domain/server_config/admin_role.rs @@ -0,0 +1,45 @@ +use crate::db::server_config::ServerConfig; +use serde::{Deserialize, Serialize}; +use tracing::{info, instrument}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum AddAdminConfigError { + UnknownError(String), +} + +#[instrument(level = "info")] +pub async fn admin_role( + server_id: u64, + role_add_id: Option, + role_remove_id: Option, +) -> Result<(), AddAdminConfigError> { + let server_config = match 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(AddAdminConfigError::UnknownError( + "Error finding server config".to_string(), + )); + } + }; + if let Some(mut config) = server_config { + if let Some(role_id) = role_add_id { + info!(id = role_id, "Adding role to admin role"); + config.admin_role.push(role_id); + } + if let Some(role_id) = role_remove_id { + info!(id = role_id, "Remove role to admin role"); + config.admin_role.retain(|&x| x != role_id); + } + match config.update().await { + Ok(_) => {} + Err(err) => { + tracing::error!(error = err.to_string(), "Error updating server config"); + return Err(AddAdminConfigError::UnknownError( + "Error updating server config".to_string(), + )); + } + } + } + Ok(()) +} 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 index 1938927..f7f1203 100644 --- 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 @@ -34,6 +34,14 @@ pub async fn check_if_server_enable_and_user_admin( match guild.member(http, UserId::new(user_id)).await { Ok(member) => { info!("Checking if user is admin"); + if member + .permissions + .into_iter() + .any(|perm| perm.administrator()) + { + info!("User has administarator permission"); + return Ok((true, Some(server_config))); + } Ok(( server_config .clone() diff --git a/src/botv2/domain/server_config/mod.rs b/src/botv2/domain/server_config/mod.rs index 3c072aa..154c274 100644 --- a/src/botv2/domain/server_config/mod.rs +++ b/src/botv2/domain/server_config/mod.rs @@ -1,3 +1,4 @@ +pub mod admin_role; pub mod change_enable_server; pub mod check_if_server_enable; pub mod check_if_server_enable_and_admin; diff --git a/src/botv2/handler.rs b/src/botv2/handler.rs index 7704047..f58fc65 100644 --- a/src/botv2/handler.rs +++ b/src/botv2/handler.rs @@ -19,7 +19,7 @@ pub async fn event_handler( serenity::FullEvent::Message { new_message } => { if new_message.author.bot || new_message.content.starts_with(&data.config.prefix.clone()) - || new_message.content.len() == 0 + || new_message.content.is_empty() { return Ok(()); } @@ -53,7 +53,7 @@ pub async fn event_handler( if !user_in_db.enable { return Ok(()); } - if config_img.keyword.len() == 0 || new_message.content.len() > 50 { + if config_img.keyword.is_empty() || new_message.content.len() > 50 { return Ok(()); } diff --git a/src/botv2/init.rs b/src/botv2/init.rs index 594f209..7961ecf 100644 --- a/src/botv2/init.rs +++ b/src/botv2/init.rs @@ -61,7 +61,7 @@ pub async fn start_bot(config: Config, rx: oneshot::Receiver<()>) -> Arc { .options(poise::FrameworkOptions { commands: vec![age(), ping(), help(), list(), enable(), answer(), server()], prefix_options: poise::PrefixFrameworkOptions { - prefix: Some(prefix.into()), + prefix: Some(prefix), ..Default::default() }, event_handler: |ctx, event, framework, data| { @@ -78,7 +78,7 @@ pub async fn start_bot(config: Config, rx: oneshot::Receiver<()>) -> Arc { entity_name: format!( "{}-{}", config.bot_name.clone(), - env!("CARGO_PKG_VERSION").to_string() + env!("CARGO_PKG_VERSION") ), }) }) diff --git a/src/db/concour.rs b/src/db/concour.rs index c899f11..a1af212 100644 --- a/src/db/concour.rs +++ b/src/db/concour.rs @@ -3,10 +3,11 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt; use surrealdb::opt::Resource; +use utoipa::ToSchema; const CONCOUR: &str = "concour"; -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] pub enum ConcourStatus { Created, Paused, @@ -20,7 +21,7 @@ impl fmt::Display for ConcourStatus { } } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] pub struct Concour { pub server_id: u64, pub enable: bool, @@ -37,12 +38,12 @@ pub struct Concour { pub winner: HashMap)>, } -impl Concour { - pub fn new(server_id: u64, channel_id: u64, enable: bool) -> Result { - Ok(Self { - server_id, - enable, - channel_id, +impl Default for Concour { + fn default() -> Self { + Self { + server_id: 0, + enable: true, + channel_id: 0, title: String::new(), description: String::new(), start_date: chrono::Utc::now(), @@ -53,6 +54,26 @@ impl Concour { index_keyword: 0, status: ConcourStatus::Created, winner: HashMap::new(), + } + } +} + +impl Concour { + pub fn new(server_id: u64, channel_id: u64) -> Result { + Ok(Self { + server_id, + enable: true, + channel_id, + title: String::new(), + description: String::new(), + start_date: chrono::Utc::now(), + periode: time::Duration::days(1), + 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> { diff --git a/src/db/server_config.rs b/src/db/server_config.rs index a3f79d1..83b4eae 100644 --- a/src/db/server_config.rs +++ b/src/db/server_config.rs @@ -2,13 +2,14 @@ use super::init::DB; use serde::{Deserialize, Serialize}; use std::fmt; use surrealdb::opt::Resource; +use utoipa::ToSchema; const SERVER_CONFIG: &str = "server_config"; const fn default_false() -> bool { false } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] pub struct ServerConfig { #[serde(default = "default_false")] pub enable: bool, diff --git a/src/db/user_image.rs b/src/db/user_image.rs index f79835b..76fb84e 100644 --- a/src/db/user_image.rs +++ b/src/db/user_image.rs @@ -1,11 +1,12 @@ use serde::{Deserialize, Serialize}; use surrealdb::opt::Resource; +use utoipa::ToSchema; use super::init::DB; const USERIMAGE: &str = "userimage"; -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] pub struct User { pub server_id: u64, pub user_id: u64, diff --git a/src/img/config_file.rs b/src/img/config_file.rs index 10f1f42..e064d81 100644 --- a/src/img/config_file.rs +++ b/src/img/config_file.rs @@ -1,6 +1,6 @@ +use poise::serenity_prelude::prelude::TypeMapKey; use regex::Regex; use serde::Deserialize; -use poise::serenity_prelude::prelude::TypeMapKey; use std::fmt::Debug; use walkdir::{DirEntry, WalkDir}; @@ -18,18 +18,18 @@ impl KeyWordItem { }) } pub fn output_folder_content(path: String) -> Vec { - let file_folder: Vec = WalkDir::new(&path) + let file_folder: Vec = WalkDir::new(path) .into_iter() .filter_entry(|_e| true) .filter(|e| { if let Some(file) = &e.as_ref().ok() { return !file.metadata().unwrap().is_dir(); } - return true; + true }) .filter_map(|e| e.ok()) .collect(); - if file_folder.len() == 0 { + if file_folder.is_empty() { return vec![]; } file_folder