Compare commits
9 commits
main
...
feat/moder
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c011d29fd | ||
|
|
38228d8cee | ||
|
|
05a1ff5c7b | ||
|
|
e7a7c0c578 | ||
|
|
83d0c0dace | ||
|
|
3385bb3660 | ||
|
|
e388ddfc8c | ||
|
|
d0bedf904c | ||
|
|
4f3badf5a8 |
6 changed files with 250 additions and 10 deletions
|
|
@ -20,13 +20,13 @@ async fn set_picture(slashcmd: &Set, ctx: &Context, cmd: &CommandInteraction, db
|
|||
_ => return Err(anyhow::anyhow!("Expected a subcommand")),
|
||||
};
|
||||
|
||||
let url = inner_options
|
||||
.iter()
|
||||
.find(|opt| opt.name == "link")
|
||||
.ok_or_else(|| anyhow::anyhow!("Option 'link' not found"))?
|
||||
.value
|
||||
.as_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("Option 'link' is not a string"))?;
|
||||
let url: &str = inner_options
|
||||
.iter()
|
||||
.find(|opt| opt.name == "link")
|
||||
.ok_or_else(|| anyhow::anyhow!("Option 'link' not found"))?
|
||||
.value
|
||||
.as_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("Option 'link' is not a string"))?;
|
||||
|
||||
let attachment: CreateAttachment = CreateAttachment::url(&ctx.http, &url)
|
||||
.await?;
|
||||
|
|
@ -85,7 +85,7 @@ impl SlashCommand for Set {
|
|||
_database: &PgPool,
|
||||
_emoji: &EmojiConfig,
|
||||
) -> Result<()> {
|
||||
debug!("Set command called");
|
||||
debug!("{} command called", self.name());
|
||||
if !is_owner(_database, &command.user.id.to_string()).await? {
|
||||
let message: CreateInteractionResponseMessage = CreateInteractionResponseMessage::new()
|
||||
.content(format!("{} | This command is only for the owner", _emoji.answer.no))
|
||||
|
|
|
|||
117
src/commands/moderation/ban.rs
Normal file
117
src/commands/moderation/ban.rs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
use crate::commands::{CommandCategory, CommandEntry, SlashCommand};
|
||||
use crate::config::EmojiConfig;
|
||||
use crate::utils::format::format_sanction_reason;
|
||||
|
||||
use serenity::all::{
|
||||
CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption, CreateInteractionResponse, CreateInteractionResponseMessage, EditInteractionResponse, GetMessages, Guild, GuildId, InteractionContext, Member, Message, MessageId, Permissions, Role, User, UserId
|
||||
};
|
||||
use sqlx::PgPool;
|
||||
use tracing::{debug, info};
|
||||
use anyhow::Result;
|
||||
|
||||
pub struct Ban;
|
||||
|
||||
#[serenity::async_trait]
|
||||
impl SlashCommand for Ban {
|
||||
fn name(&self) -> &'static str {
|
||||
"ban"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Ban the user provided"
|
||||
}
|
||||
|
||||
fn category(&self) -> &'static CommandCategory {
|
||||
&CommandCategory::Moderation
|
||||
}
|
||||
|
||||
fn register(&self) -> CreateCommand {
|
||||
info!("\t✅ | {}", self.name());
|
||||
let mut options: Vec<CreateCommandOption> = Vec::new();
|
||||
|
||||
let target: CreateCommandOption = CreateCommandOption::new(CommandOptionType::User, "user", "The user to ban")
|
||||
.required(true);
|
||||
options.push(target);
|
||||
|
||||
let reason: CreateCommandOption = CreateCommandOption::new(CommandOptionType::String, "reason", "The reason to ban this user")
|
||||
.required(false);
|
||||
options.push(reason);
|
||||
|
||||
CreateCommand::new(self.name())
|
||||
.description(self.description())
|
||||
.default_member_permissions(Permissions::BAN_MEMBERS)
|
||||
.set_options(options)
|
||||
.contexts(vec![
|
||||
InteractionContext::Guild,
|
||||
])
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
ctx: &Context,
|
||||
command: &CommandInteraction,
|
||||
_database: &PgPool,
|
||||
_emoji: &EmojiConfig,
|
||||
) -> Result<()> {
|
||||
debug!("{} command called", self.name());
|
||||
let target_id: UserId = command.data.options.iter()
|
||||
.find(|opt| opt.kind() == CommandOptionType::User)
|
||||
.and_then(|opt| opt.value.as_user_id())
|
||||
.ok_or_else(|| anyhow::anyhow!("Aucun utilisateur spécifié"))?;
|
||||
let target: User = command.data.resolved.users.get(&target_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("Utilisateur introuvable"))?
|
||||
.clone();
|
||||
let reason_provided: Option<&str> = command.data.options.iter().find(|opt | opt.kind() == CommandOptionType::String).and_then(|opt| opt.value.as_str());
|
||||
let reason_formatted: String = format_sanction_reason("ban", reason_provided, &command.user.name);
|
||||
|
||||
let guild_id: GuildId = command.guild_id.ok_or(anyhow::anyhow!("Ban command executed in DM"))?;
|
||||
let guild: Guild = ctx.cache.guild(guild_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("Guild not found in cache"))?
|
||||
.clone();
|
||||
|
||||
let target_member: Member = guild_id.member(&ctx.http, target_id).await?;
|
||||
let executor_member: Member = guild_id.member(&ctx.http, command.user.id).await?;
|
||||
let bot_id: UserId = ctx.cache.current_user().id;
|
||||
let bot_member: Member = guild_id.member(&ctx.http, bot_id).await?;
|
||||
|
||||
if reason_formatted.len() > 512 {
|
||||
let message: CreateInteractionResponseMessage = CreateInteractionResponseMessage::new()
|
||||
.content(format!("{} | Reason too long (> 512 char)", _emoji.answer.error))
|
||||
.ephemeral(true);
|
||||
let response: CreateInteractionResponse = CreateInteractionResponse::Message(message);
|
||||
command.create_response(&ctx.http, response).await?;
|
||||
return Ok(());
|
||||
}
|
||||
let target_role_pos: u16 = guild
|
||||
.member_highest_role(&target_member)
|
||||
.map(|r| r.position)
|
||||
.unwrap_or(0);
|
||||
let executor_role_pos: u16 = guild
|
||||
.member_highest_role(&executor_member)
|
||||
.map(|r| r.position)
|
||||
.unwrap_or(0);
|
||||
let bot_role_pos: u16 = guild
|
||||
.member_highest_role(&bot_member)
|
||||
.map(|r| r.position)
|
||||
.unwrap_or(0);
|
||||
if target_role_pos > executor_role_pos || target_role_pos > bot_role_pos || target_id == guild.owner_id {
|
||||
let message: CreateInteractionResponseMessage = CreateInteractionResponseMessage::new()
|
||||
.content(format!("{} | You cannot ban this user because they are hierarchically above you", _emoji.answer.error))
|
||||
.ephemeral(true);
|
||||
let response: CreateInteractionResponse = CreateInteractionResponse::Message(message);
|
||||
command.create_response(&ctx.http, response).await?;
|
||||
return Ok(());
|
||||
}
|
||||
guild_id.ban_with_reason(&ctx.http, target.id, 0, &reason_formatted).await?;
|
||||
let message: CreateInteractionResponseMessage = CreateInteractionResponseMessage::new()
|
||||
.content(format!("{} | {} is now ban", _emoji.answer.yes, target.name))
|
||||
.ephemeral(true);
|
||||
let response: CreateInteractionResponse = CreateInteractionResponse::Message(message);
|
||||
command.create_response(&ctx.http, response).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
inventory::submit! {
|
||||
CommandEntry { create: || Box::new(Ban) }
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ impl SlashCommand for Clear {
|
|||
_database: &PgPool,
|
||||
_emoji: &EmojiConfig,
|
||||
) -> Result<()> {
|
||||
debug!("Clear command called");
|
||||
debug!("{} command called", self.name());
|
||||
let amount: u8 = command.data.options.iter().find(|opt | opt.kind() == CommandOptionType::Integer)
|
||||
.unwrap().value.as_i64().expect("REASON") as u8;
|
||||
let message: CreateInteractionResponseMessage = CreateInteractionResponseMessage::new()
|
||||
|
|
|
|||
117
src/commands/moderation/kick.rs
Normal file
117
src/commands/moderation/kick.rs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
use crate::commands::{CommandCategory, CommandEntry, SlashCommand};
|
||||
use crate::config::EmojiConfig;
|
||||
use crate::utils::format::format_sanction_reason;
|
||||
|
||||
use serenity::all::{
|
||||
CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption, CreateInteractionResponse, CreateInteractionResponseMessage, EditInteractionResponse, GetMessages, Guild, GuildId, InteractionContext, Member, Message, MessageId, Permissions, Role, User, UserId
|
||||
};
|
||||
use sqlx::PgPool;
|
||||
use tracing::{debug, info};
|
||||
use anyhow::Result;
|
||||
|
||||
pub struct Kick;
|
||||
|
||||
#[serenity::async_trait]
|
||||
impl SlashCommand for Kick {
|
||||
fn name(&self) -> &'static str {
|
||||
"kick"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
"Kick the user provided"
|
||||
}
|
||||
|
||||
fn category(&self) -> &'static CommandCategory {
|
||||
&CommandCategory::Moderation
|
||||
}
|
||||
|
||||
fn register(&self) -> CreateCommand {
|
||||
info!("\t✅ | {}", self.name());
|
||||
let mut options: Vec<CreateCommandOption> = Vec::new();
|
||||
|
||||
let target: CreateCommandOption = CreateCommandOption::new(CommandOptionType::User, "user", "The user to kick")
|
||||
.required(true);
|
||||
options.push(target);
|
||||
|
||||
let reason: CreateCommandOption = CreateCommandOption::new(CommandOptionType::String, "reason", "The reason to kick this user")
|
||||
.required(false);
|
||||
options.push(reason);
|
||||
|
||||
CreateCommand::new(self.name())
|
||||
.description(self.description())
|
||||
.default_member_permissions(Permissions::KICK_MEMBERS)
|
||||
.set_options(options)
|
||||
.contexts(vec![
|
||||
InteractionContext::Guild,
|
||||
])
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
ctx: &Context,
|
||||
command: &CommandInteraction,
|
||||
_database: &PgPool,
|
||||
_emoji: &EmojiConfig,
|
||||
) -> Result<()> {
|
||||
debug!("{} command called", self.name());
|
||||
let target_id: UserId = command.data.options.iter()
|
||||
.find(|opt| opt.kind() == CommandOptionType::User)
|
||||
.and_then(|opt| opt.value.as_user_id())
|
||||
.ok_or_else(|| anyhow::anyhow!("Aucun utilisateur spécifié"))?;
|
||||
let target: User = command.data.resolved.users.get(&target_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("Utilisateur introuvable"))?
|
||||
.clone();
|
||||
let reason_provided: Option<&str> = command.data.options.iter().find(|opt | opt.kind() == CommandOptionType::String).and_then(|opt| opt.value.as_str());
|
||||
let reason_formatted: String = format_sanction_reason("kick", reason_provided, &command.user.name);
|
||||
|
||||
let guild_id: GuildId = command.guild_id.ok_or(anyhow::anyhow!("Kick command executed in DM"))?;
|
||||
let guild: Guild = ctx.cache.guild(guild_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("Guild not found in cache"))?
|
||||
.clone();
|
||||
|
||||
let target_member: Member = guild_id.member(&ctx.http, target_id).await?;
|
||||
let executor_member: Member = guild_id.member(&ctx.http, command.user.id).await?;
|
||||
let bot_id: UserId = ctx.cache.current_user().id;
|
||||
let bot_member: Member = guild_id.member(&ctx.http, bot_id).await?;
|
||||
|
||||
if reason_formatted.len() > 512 {
|
||||
let message: CreateInteractionResponseMessage = CreateInteractionResponseMessage::new()
|
||||
.content(format!("{} | Reason too long (> 512 char)", _emoji.answer.error))
|
||||
.ephemeral(true);
|
||||
let response: CreateInteractionResponse = CreateInteractionResponse::Message(message);
|
||||
command.create_response(&ctx.http, response).await?;
|
||||
return Ok(());
|
||||
}
|
||||
let target_role_pos: u16 = guild
|
||||
.member_highest_role(&target_member)
|
||||
.map(|r| r.position)
|
||||
.unwrap_or(0);
|
||||
let executor_role_pos: u16 = guild
|
||||
.member_highest_role(&executor_member)
|
||||
.map(|r| r.position)
|
||||
.unwrap_or(0);
|
||||
let bot_role_pos: u16 = guild
|
||||
.member_highest_role(&bot_member)
|
||||
.map(|r| r.position)
|
||||
.unwrap_or(0);
|
||||
if target_role_pos > executor_role_pos || target_role_pos > bot_role_pos || target_id == guild.owner_id {
|
||||
let message: CreateInteractionResponseMessage = CreateInteractionResponseMessage::new()
|
||||
.content(format!("{} | You cannot kick this user because they are hierarchically above you", _emoji.answer.error))
|
||||
.ephemeral(true);
|
||||
let response: CreateInteractionResponse = CreateInteractionResponse::Message(message);
|
||||
command.create_response(&ctx.http, response).await?;
|
||||
return Ok(());
|
||||
}
|
||||
guild_id.kick_with_reason(&ctx.http, target.id, &reason_formatted).await?;
|
||||
let message: CreateInteractionResponseMessage = CreateInteractionResponseMessage::new()
|
||||
.content(format!("{} | {} is now kick", _emoji.answer.yes, target.name))
|
||||
.ephemeral(true);
|
||||
let response: CreateInteractionResponse = CreateInteractionResponse::Message(message);
|
||||
command.create_response(&ctx.http, response).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
inventory::submit! {
|
||||
CommandEntry { create: || Box::new(Kick) }
|
||||
}
|
||||
|
|
@ -94,7 +94,7 @@ impl SlashCommand for Help {
|
|||
_database: &PgPool,
|
||||
_emoji: &EmojiConfig,
|
||||
) -> Result<()> {
|
||||
debug!("Help command called");
|
||||
debug!("{} command called", self.name());
|
||||
let guild: GuildId = command.guild_id.ok_or(serenity::Error::Other("Commande non disponible en DM"))?;
|
||||
let guild_id: String = guild.to_string();
|
||||
let guild_db: Option<DbGuild> = guild::get(_database, &guild_id).await.map_err(|_e| serenity::Error::Other("Database error guild on help command"))?;
|
||||
|
|
|
|||
6
src/utils/format.rs
Normal file
6
src/utils/format.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
pub fn format_sanction_reason(module_name:&str, reason: Option<&str>, executor: &str) -> String {
|
||||
match reason {
|
||||
Some(s) => format!("[TTY {}] {} by {}", module_name, s, executor),
|
||||
None => format!("[TTY {}] by {}", module_name, executor)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue