feat: add feature: add admin, handling of the user has administrator perm, and start working on the function that the bot has

This commit is contained in:
Max batleforc 2024-06-21 10:16:38 +02:00
parent 7524fe07e2
commit f7fefb1ab6
No known key found for this signature in database
GPG Key ID: 25D243AB4B6AC9E7
19 changed files with 291 additions and 32 deletions

View File

@ -1,6 +1,7 @@
use utoipa::OpenApi; use utoipa::OpenApi;
use crate::api::bot::info; use crate::api::bot::info;
use crate::db::{concour, server_config, user_image};
#[derive(OpenApi)] #[derive(OpenApi)]
#[openapi( #[openapi(
@ -18,6 +19,10 @@ use crate::api::bot::info;
schemas( schemas(
info::Info, info::Info,
info::InfoGuild, info::InfoGuild,
server_config::ServerConfig,
user_image::User,
concour::Concour,
concour::ConcourStatus,
) )
), ),
paths( paths(

View File

@ -27,16 +27,20 @@ pub async fn answer(
if let Ok(answer) = answer { if let Ok(answer) = answer {
match answer { match answer {
AnswerResult::Embed(embeds, attachments) => { AnswerResult::Embed(embeds, attachments) => {
let mut reply = CreateReply::default(); let reply = CreateReply {
reply.embeds = embeds; embeds,
reply.attachments = attachments; attachments,
..Default::default()
};
if let Err(why) = ctx.send(reply).await { if let Err(why) = ctx.send(reply).await {
tracing::error!("Error sending message: {:?}", why); tracing::error!("Error sending message: {:?}", why);
} }
} }
AnswerResult::Animated(attachments) => { AnswerResult::Animated(attachments) => {
let mut reply = CreateReply::default(); let reply = CreateReply {
reply.attachments = attachments; attachments,
..Default::default()
};
if let Err(why) = ctx.send(reply).await { if let Err(why) = ctx.send(reply).await {
tracing::error!("Error sending message: {:?}", why); tracing::error!("Error sending message: {:?}", why);
} }

View File

@ -30,13 +30,13 @@ pub async fn list(ctx: Context<'_>) -> Result<(), Error> {
.fields(chunks.to_vec()) .fields(chunks.to_vec())
.footer(footer) .footer(footer)
}); });
let mut reply = CreateReply::default(); let reply = CreateReply {
reply.embeds = embed_vec.collect(); embeds: embed_vec.collect(),
..Default::default()
};
if let Err(why) = ctx.send(reply).await { if let Err(why) = ctx.send(reply).await {
tracing::error!("Error sending message: {:?}", why); tracing::error!("Error sending message: {:?}", why);
} }
Ok(()) Ok(())
} }
// https://github.com/serenity-rs/poise/blob/current/examples/fluent_localization/main.rs

View File

@ -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<Role>,
#[description = "Remove admin role"] remove: Option<Role>,
) -> 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(())
}

View File

@ -1,7 +1,8 @@
use crate::botv2::{ use crate::botv2::{
domain::server_config::{ domain::server_config::{
change_enable_server::change_enable_server,
check_if_server_enable_and_admin::check_if_server_enable_and_user_admin, 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}, init::{Context, Error},
}; };
@ -63,7 +64,7 @@ pub async fn feature(
} }
}; };
let answer_auto_meme = match auto_meme { 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( Ok(_) => Some(
CreateEmbed::new() CreateEmbed::new()
.title(format!("Auto meme feature initialized: {}", enable)) .title(format!("Auto meme feature initialized: {}", enable))
@ -78,7 +79,7 @@ pub async fn feature(
None => None, None => None,
}; };
let answer_enable_concour = match concour { 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( Ok(_) => Some(
CreateEmbed::new() CreateEmbed::new()
.title(format!("Concour feature initialized: {}", enable)) .title(format!("Concour feature initialized: {}", enable))

View File

@ -1,3 +1,4 @@
pub mod admin;
pub mod feature; pub mod feature;
pub mod init_server; pub mod init_server;
pub mod server; pub mod server;

View File

@ -1,5 +1,5 @@
use crate::botv2::{ use crate::botv2::{
cmd::server_config::{feature::feature, init_server::init}, cmd::server_config::{admin::admin, feature::feature, init_server::init},
domain::server_config::{ domain::server_config::{
check_if_server_enable_and_admin::check_if_server_enable_and_user_admin, check_if_server_enable_and_admin::check_if_server_enable_and_user_admin,
get_server_config::get_server_config, get_server_config::get_server_config,
@ -18,7 +18,7 @@ use tracing::instrument;
slash_command, slash_command,
prefix_command, prefix_command,
category = "server_config", category = "server_config",
subcommands("init", "info", "feature"), subcommands("init", "info", "feature", "admin"),
guild_only = true guild_only = true
)] )]
pub async fn server( pub async fn server(

View File

@ -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<Option<Concour>, 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))
}

View File

@ -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<Option<Concour>, 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)
}

View File

@ -1,2 +1,4 @@
pub mod check_if_allowed; pub mod check_if_allowed;
pub mod create_concour;
pub mod get_channel_concour;
pub mod list_concour; pub mod list_concour;

View File

@ -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<u64>,
role_remove_id: Option<u64>,
) -> 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(())
}

View File

@ -34,6 +34,14 @@ pub async fn check_if_server_enable_and_user_admin(
match guild.member(http, UserId::new(user_id)).await { match guild.member(http, UserId::new(user_id)).await {
Ok(member) => { Ok(member) => {
info!("Checking if user is admin"); 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(( Ok((
server_config server_config
.clone() .clone()

View File

@ -1,3 +1,4 @@
pub mod admin_role;
pub mod change_enable_server; pub mod change_enable_server;
pub mod check_if_server_enable; pub mod check_if_server_enable;
pub mod check_if_server_enable_and_admin; pub mod check_if_server_enable_and_admin;

View File

@ -19,7 +19,7 @@ pub async fn event_handler(
serenity::FullEvent::Message { new_message } => { serenity::FullEvent::Message { new_message } => {
if new_message.author.bot if new_message.author.bot
|| new_message.content.starts_with(&data.config.prefix.clone()) || new_message.content.starts_with(&data.config.prefix.clone())
|| new_message.content.len() == 0 || new_message.content.is_empty()
{ {
return Ok(()); return Ok(());
} }
@ -53,7 +53,7 @@ pub async fn event_handler(
if !user_in_db.enable { if !user_in_db.enable {
return Ok(()); 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(()); return Ok(());
} }

View File

@ -61,7 +61,7 @@ pub async fn start_bot(config: Config, rx: oneshot::Receiver<()>) -> Arc<Http> {
.options(poise::FrameworkOptions { .options(poise::FrameworkOptions {
commands: vec![age(), ping(), help(), list(), enable(), answer(), server()], commands: vec![age(), ping(), help(), list(), enable(), answer(), server()],
prefix_options: poise::PrefixFrameworkOptions { prefix_options: poise::PrefixFrameworkOptions {
prefix: Some(prefix.into()), prefix: Some(prefix),
..Default::default() ..Default::default()
}, },
event_handler: |ctx, event, framework, data| { event_handler: |ctx, event, framework, data| {
@ -78,7 +78,7 @@ pub async fn start_bot(config: Config, rx: oneshot::Receiver<()>) -> Arc<Http> {
entity_name: format!( entity_name: format!(
"{}-{}", "{}-{}",
config.bot_name.clone(), config.bot_name.clone(),
env!("CARGO_PKG_VERSION").to_string() env!("CARGO_PKG_VERSION")
), ),
}) })
}) })

View File

@ -3,10 +3,11 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
use surrealdb::opt::Resource; use surrealdb::opt::Resource;
use utoipa::ToSchema;
const CONCOUR: &str = "concour"; const CONCOUR: &str = "concour";
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub enum ConcourStatus { pub enum ConcourStatus {
Created, Created,
Paused, 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 struct Concour {
pub server_id: u64, pub server_id: u64,
pub enable: bool, pub enable: bool,
@ -37,12 +38,12 @@ pub struct Concour {
pub winner: HashMap<String, (u64, chrono::DateTime<chrono::Utc>)>, pub winner: HashMap<String, (u64, chrono::DateTime<chrono::Utc>)>,
} }
impl Concour { impl Default for Concour {
pub fn new(server_id: u64, channel_id: u64, enable: bool) -> Result<Self, surrealdb::Error> { fn default() -> Self {
Ok(Self { Self {
server_id, server_id: 0,
enable, enable: true,
channel_id, channel_id: 0,
title: String::new(), title: String::new(),
description: String::new(), description: String::new(),
start_date: chrono::Utc::now(), start_date: chrono::Utc::now(),
@ -53,6 +54,26 @@ impl Concour {
index_keyword: 0, index_keyword: 0,
status: ConcourStatus::Created, status: ConcourStatus::Created,
winner: HashMap::new(), winner: HashMap::new(),
}
}
}
impl Concour {
pub fn new(server_id: u64, channel_id: u64) -> Result<Self, surrealdb::Error> {
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> { pub async fn create(&self) -> Result<(), surrealdb::Error> {

View File

@ -2,13 +2,14 @@ use super::init::DB;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use surrealdb::opt::Resource; use surrealdb::opt::Resource;
use utoipa::ToSchema;
const SERVER_CONFIG: &str = "server_config"; const SERVER_CONFIG: &str = "server_config";
const fn default_false() -> bool { const fn default_false() -> bool {
false false
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct ServerConfig { pub struct ServerConfig {
#[serde(default = "default_false")] #[serde(default = "default_false")]
pub enable: bool, pub enable: bool,

View File

@ -1,11 +1,12 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use surrealdb::opt::Resource; use surrealdb::opt::Resource;
use utoipa::ToSchema;
use super::init::DB; use super::init::DB;
const USERIMAGE: &str = "userimage"; const USERIMAGE: &str = "userimage";
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
pub struct User { pub struct User {
pub server_id: u64, pub server_id: u64,
pub user_id: u64, pub user_id: u64,

View File

@ -1,6 +1,6 @@
use poise::serenity_prelude::prelude::TypeMapKey;
use regex::Regex; use regex::Regex;
use serde::Deserialize; use serde::Deserialize;
use poise::serenity_prelude::prelude::TypeMapKey;
use std::fmt::Debug; use std::fmt::Debug;
use walkdir::{DirEntry, WalkDir}; use walkdir::{DirEntry, WalkDir};
@ -18,18 +18,18 @@ impl KeyWordItem {
}) })
} }
pub fn output_folder_content(path: String) -> Vec<DirEntry> { pub fn output_folder_content(path: String) -> Vec<DirEntry> {
let file_folder: Vec<DirEntry> = WalkDir::new(&path) let file_folder: Vec<DirEntry> = WalkDir::new(path)
.into_iter() .into_iter()
.filter_entry(|_e| true) .filter_entry(|_e| true)
.filter(|e| { .filter(|e| {
if let Some(file) = &e.as_ref().ok() { if let Some(file) = &e.as_ref().ok() {
return !file.metadata().unwrap().is_dir(); return !file.metadata().unwrap().is_dir();
} }
return true; true
}) })
.filter_map(|e| e.ok()) .filter_map(|e| e.ok())
.collect(); .collect();
if file_folder.len() == 0 { if file_folder.is_empty() {
return vec![]; return vec![];
} }
file_folder file_folder