mirror of
https://github.com/arthur-pbty/shadowbot.git
synced 2026-06-15 08:13:26 +02:00
netoyage et optimisation des commande pour que elle soit trier correctement
This commit is contained in:
@@ -1,53 +0,0 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::permissions::is_owner_user;
|
||||
|
||||
pub fn parse_user_id(input: &str) -> Option<UserId> {
|
||||
let cleaned = input
|
||||
.trim()
|
||||
.trim_start_matches('<')
|
||||
.trim_end_matches('>')
|
||||
.trim_start_matches('@')
|
||||
.trim_start_matches('!');
|
||||
|
||||
cleaned.parse::<u64>().ok().map(UserId::new)
|
||||
}
|
||||
|
||||
pub async fn app_owner_id(ctx: &Context) -> Option<UserId> {
|
||||
let info = ctx.http.get_current_application_info().await.ok()?;
|
||||
info.owner.map(|u| u.id)
|
||||
}
|
||||
|
||||
pub async fn ensure_owner(ctx: &Context, msg: &Message) -> Result<(), ()> {
|
||||
if is_owner_user(ctx, msg.author.id).await {
|
||||
Ok(())
|
||||
} else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Accès refusé")
|
||||
.description("Cette commande est réservée aux owners du bot.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ban_user_everywhere(ctx: &Context, user_id: UserId, reason: &str) -> (usize, usize) {
|
||||
let guilds = ctx.cache.guilds();
|
||||
let mut ok = 0usize;
|
||||
let mut ko = 0usize;
|
||||
|
||||
for guild_id in guilds {
|
||||
match guild_id
|
||||
.ban_with_reason(&ctx.http, user_id, 0, reason)
|
||||
.await
|
||||
{
|
||||
Ok(_) => ok += 1,
|
||||
Err(_) => ko += 1,
|
||||
}
|
||||
}
|
||||
|
||||
(ok, ko)
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::db::{DbPoolKey, is_blacklisted, list_blacklisted_ids};
|
||||
|
||||
pub async fn enforce_blacklist_on_message(ctx: &Context, msg: &Message) -> bool {
|
||||
if msg.author.bot {
|
||||
return false;
|
||||
}
|
||||
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let pool = {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
};
|
||||
|
||||
let Some(pool) = pool else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let blacklisted = is_blacklisted(&pool, bot_id, msg.author.id)
|
||||
.await
|
||||
.unwrap_or(false);
|
||||
if !blacklisted {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(guild_id) = msg.guild_id {
|
||||
let _ = guild_id
|
||||
.ban_with_reason(&ctx.http, msg.author.id, 0, "Blacklist globale du bot")
|
||||
.await;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub async fn enforce_blacklist_on_guild(ctx: &Context, guild_id: GuildId) {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let pool = {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
};
|
||||
|
||||
let Some(pool) = pool else {
|
||||
return;
|
||||
};
|
||||
|
||||
let users = list_blacklisted_ids(&pool, bot_id)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
for uid in users {
|
||||
let _ = guild_id
|
||||
.ban_with_reason(&ctx.http, uid, 0, "Blacklist globale du bot")
|
||||
.await;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,49 +12,13 @@ pub async fn handle_autopublish(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
};
|
||||
|
||||
if args.is_empty() {
|
||||
let Some(pool) = ({
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<db::DbPoolKey>().cloned()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let channels = db::get_autopublish_channels(&pool, bot_id, guild_id.get() as i64)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let description = if channels.is_empty() {
|
||||
"Aucun salon d'annonces configuré.".to_string()
|
||||
} else {
|
||||
channels
|
||||
.into_iter()
|
||||
.map(|channel| format!("<#{}>", channel.channel_id))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
if !args.is_empty() {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Autopublish")
|
||||
.description(description)
|
||||
.colour(Colour::from_rgb(100, 150, 255)),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let enabled = args[0].eq_ignore_ascii_case("on") || args[0].eq_ignore_ascii_case("enable");
|
||||
let disabled = args[0].eq_ignore_ascii_case("off") || args[0].eq_ignore_ascii_case("disable");
|
||||
if !enabled && !disabled {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Autopublish")
|
||||
.description("Utilisation: +autopublish on|off [#canal]")
|
||||
.description("Utilisation: +autopublish")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
@@ -68,18 +32,53 @@ pub async fn handle_autopublish(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_i64 = guild_id.get() as i64;
|
||||
|
||||
let channels = db::get_autopublish_channels(&pool, bot_id, guild_id_i64)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let description = if channels.is_empty() {
|
||||
"Aucun salon d'annonces configuré.".to_string()
|
||||
} else {
|
||||
channels
|
||||
.into_iter()
|
||||
.map(|channel| format!("<#{}>", channel.channel_id))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Autopublish")
|
||||
.description(description)
|
||||
.colour(Colour::from_rgb(100, 150, 255)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_autopublishon(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = ({
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<db::DbPoolKey>().cloned()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_i64 = guild_id.get() as i64;
|
||||
let channel_id = args
|
||||
.get(1)
|
||||
.first()
|
||||
.and_then(|value| parse_channel_id(value))
|
||||
.unwrap_or(msg.channel_id);
|
||||
|
||||
let result = if enabled {
|
||||
db::add_autopublish_channel(&pool, bot_id, guild_id_i64, channel_id.get() as i64).await
|
||||
} else {
|
||||
db::remove_autopublish_channel(&pool, bot_id, guild_id_i64, channel_id.get() as i64).await
|
||||
};
|
||||
let result = db::add_autopublish_channel(&pool, bot_id, guild_id_i64, channel_id.get() as i64).await;
|
||||
|
||||
if result.is_err() {
|
||||
send_embed(
|
||||
@@ -94,21 +93,63 @@ pub async fn handle_autopublish(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let embed = if enabled {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Autopublish activé")
|
||||
.description(format!("Salon: <#{}>", channel_id.get()))
|
||||
.colour(Colour::from_rgb(0, 200, 120))
|
||||
.timestamp(Utc::now())
|
||||
} else {
|
||||
.timestamp(Utc::now()),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_autopublishoff(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = ({
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<db::DbPoolKey>().cloned()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_i64 = guild_id.get() as i64;
|
||||
let channel_id = args
|
||||
.first()
|
||||
.and_then(|value| parse_channel_id(value))
|
||||
.unwrap_or(msg.channel_id);
|
||||
|
||||
let result =
|
||||
db::remove_autopublish_channel(&pool, bot_id, guild_id_i64, channel_id.get() as i64).await;
|
||||
|
||||
if result.is_err() {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Autopublish")
|
||||
.description("Impossible de mettre à jour le salon d'annonces.")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Autopublish désactivé")
|
||||
.description(format!("Salon: <#{}>", channel_id.get()))
|
||||
.colour(Colour::from_rgb(255, 120, 0))
|
||||
.timestamp(Utc::now())
|
||||
};
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
.timestamp(Utc::now()),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct AutopublishCommand;
|
||||
@@ -119,13 +160,9 @@ impl crate::commands::command_contract::CommandSpec for AutopublishCommand {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "autopublish",
|
||||
category: "automation",
|
||||
params: "on|off [#canal]",
|
||||
description: "Affiche, active ou desactive la publication automatique des annonces.",
|
||||
examples: &[
|
||||
"+autopublish",
|
||||
"+autopublish on #annonces",
|
||||
"+help autopublish",
|
||||
],
|
||||
params: "aucun",
|
||||
description: "Affiche les salons ou la publication automatique des annonces est active.",
|
||||
examples: &["+autopublish", "+help autopublish"],
|
||||
default_aliases: &["apb"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 5,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_autopublishoff_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::autopublish::handle_autopublishoff(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct AutopublishoffCommand;
|
||||
pub static COMMAND_DESCRIPTOR: AutopublishoffCommand = AutopublishoffCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for AutopublishoffCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "autopublishoff",
|
||||
category: "automation",
|
||||
params: "[#canal]",
|
||||
description: "Desactive la publication automatique des annonces sur un salon.",
|
||||
examples: &["+autopublishoff", "+autopublishoff #annonces", "+help autopublishoff"],
|
||||
default_aliases: &["apboff"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_autopublishon_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::autopublish::handle_autopublishon(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct AutopublishonCommand;
|
||||
pub static COMMAND_DESCRIPTOR: AutopublishonCommand = AutopublishonCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for AutopublishonCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "autopublishon",
|
||||
category: "automation",
|
||||
params: "[#canal]",
|
||||
description: "Active la publication automatique des annonces sur un salon.",
|
||||
examples: &["+autopublishon", "+autopublishon #annonces", "+help autopublishon"],
|
||||
default_aliases: &["apbon"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,61 +106,65 @@ pub async fn handle_piconly(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_i64 = guild_id.get() as i64;
|
||||
|
||||
if args.is_empty() {
|
||||
let channels = db::get_piconly_channels(&pool, bot_id, guild_id_i64)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let description = if channels.is_empty() {
|
||||
"Aucun salon selfie configure.".to_string()
|
||||
} else {
|
||||
channels
|
||||
.into_iter()
|
||||
.map(|channel| format!("<#{}>", channel.channel_id))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
if !args.is_empty() {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("PicOnly")
|
||||
.description(description)
|
||||
.timestamp(Utc::now()),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let adding = args[0].eq_ignore_ascii_case("add");
|
||||
let deleting = args[0].eq_ignore_ascii_case("del")
|
||||
|| args[0].eq_ignore_ascii_case("remove")
|
||||
|| args[0].eq_ignore_ascii_case("delete");
|
||||
|
||||
if !adding && !deleting {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("PicOnly")
|
||||
.description("Utilisation: +piconly <add/del> [#salon]")
|
||||
.description("Utilisation: +piconly")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let channels = db::get_piconly_channels(&pool, bot_id, guild_id_i64)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let description = if channels.is_empty() {
|
||||
"Aucun salon selfie configure.".to_string()
|
||||
} else {
|
||||
channels
|
||||
.into_iter()
|
||||
.map(|channel| format!("<#{}>", channel.channel_id))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("PicOnly")
|
||||
.description(description)
|
||||
.timestamp(Utc::now()),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_piconlyadd(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = ({
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<db::DbPoolKey>().cloned()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_i64 = guild_id.get() as i64;
|
||||
|
||||
let channel_id = args
|
||||
.get(1)
|
||||
.first()
|
||||
.and_then(|raw| parse_channel_id(raw))
|
||||
.unwrap_or(msg.channel_id);
|
||||
|
||||
let result = if adding {
|
||||
db::add_piconly_channel(&pool, bot_id, guild_id_i64, channel_id.get() as i64).await
|
||||
} else {
|
||||
db::remove_piconly_channel(&pool, bot_id, guild_id_i64, channel_id.get() as i64).await
|
||||
};
|
||||
let result = db::add_piconly_channel(&pool, bot_id, guild_id_i64, channel_id.get() as i64).await;
|
||||
|
||||
if result.is_err() {
|
||||
send_embed(
|
||||
@@ -175,19 +179,61 @@ pub async fn handle_piconly(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let embed = if adding {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Salon selfie ajoute")
|
||||
.description(format!("Salon: <#{}>", channel_id.get()))
|
||||
.timestamp(Utc::now())
|
||||
} else {
|
||||
.timestamp(Utc::now()),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_piconlydel(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = ({
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<db::DbPoolKey>().cloned()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_i64 = guild_id.get() as i64;
|
||||
|
||||
let channel_id = args
|
||||
.first()
|
||||
.and_then(|raw| parse_channel_id(raw))
|
||||
.unwrap_or(msg.channel_id);
|
||||
|
||||
let result = db::remove_piconly_channel(&pool, bot_id, guild_id_i64, channel_id.get() as i64).await;
|
||||
|
||||
if result.is_err() {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("PicOnly")
|
||||
.description("Impossible de mettre a jour le salon selfie.")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Salon selfie retire")
|
||||
.description(format!("Salon: <#{}>", channel_id.get()))
|
||||
.timestamp(Utc::now())
|
||||
};
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
.timestamp(Utc::now()),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct PiconlyCommand;
|
||||
@@ -198,9 +244,9 @@ impl crate::commands::command_contract::CommandSpec for PiconlyCommand {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "piconly",
|
||||
category: "automation",
|
||||
params: "<add/del> [salon]",
|
||||
description: "Definit ou supprime un salon selfie, ou les membres ne peuvent envoyer que des photos.",
|
||||
examples: &["+piconly", "+piconly add #selfie", "+piconly del #selfie"],
|
||||
params: "aucun",
|
||||
description: "Affiche la liste des salons selfie, ou les membres ne peuvent envoyer que des photos.",
|
||||
examples: &["+piconly", "+help piconly"],
|
||||
default_aliases: &["selfieonly"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 6,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_piconlyadd_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::piconly::handle_piconlyadd(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct PiconlyaddCommand;
|
||||
pub static COMMAND_DESCRIPTOR: PiconlyaddCommand = PiconlyaddCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for PiconlyaddCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "piconlyadd",
|
||||
category: "automation",
|
||||
params: "[#salon]",
|
||||
description: "Ajoute un salon selfie (photos uniquement).",
|
||||
examples: &["+piconlyadd", "+piconlyadd #selfie", "+help piconlyadd"],
|
||||
default_aliases: &["selfieadd"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 6,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_piconlydel_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::piconly::handle_piconlydel(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct PiconlydelCommand;
|
||||
pub static COMMAND_DESCRIPTOR: PiconlydelCommand = PiconlydelCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for PiconlydelCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "piconlydel",
|
||||
category: "automation",
|
||||
params: "[#salon]",
|
||||
description: "Retire un salon selfie (photos uniquement).",
|
||||
examples: &["+piconlydel", "+piconlydel #selfie", "+help piconlydel"],
|
||||
default_aliases: &["selfiedel"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 6,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,602 +0,0 @@
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use chrono::Utc;
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::commands::moderation_sanction_helpers::{add_sanction, handle_timeout};
|
||||
use crate::db::{
|
||||
self, DbPoolKey, ModerationSettings, PunishRule, count_member_strikes_in_window,
|
||||
ensure_default_punish_rules, get_last_punish_triggered_at, upsert_last_punish_triggered_at,
|
||||
};
|
||||
use crate::permissions;
|
||||
|
||||
static SPAM_TRACKER: OnceLock<Mutex<HashMap<(u64, u64, u64), VecDeque<Instant>>>> = OnceLock::new();
|
||||
|
||||
pub async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
}
|
||||
|
||||
pub fn parse_on_off(input: &str) -> Option<bool> {
|
||||
match input.trim().to_lowercase().as_str() {
|
||||
"on" | "enable" | "enabled" | "true" | "1" => Some(true),
|
||||
"off" | "disable" | "disabled" | "false" | "0" => Some(false),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_duration_to_seconds(input: &str) -> Option<i64> {
|
||||
let raw = input.trim().to_lowercase();
|
||||
if raw.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut digits = String::new();
|
||||
let mut suffix = String::new();
|
||||
|
||||
for ch in raw.chars() {
|
||||
if ch.is_ascii_digit() {
|
||||
if !suffix.is_empty() {
|
||||
return None;
|
||||
}
|
||||
digits.push(ch);
|
||||
} else if !ch.is_whitespace() {
|
||||
suffix.push(ch);
|
||||
}
|
||||
}
|
||||
|
||||
let value = digits.parse::<i64>().ok()?;
|
||||
if value <= 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let unit = if suffix.is_empty() { "s" } else { &suffix };
|
||||
let seconds = match unit {
|
||||
"s" | "sec" | "secs" | "seconde" | "secondes" => value,
|
||||
"m" | "min" | "mins" | "minute" | "minutes" => value.checked_mul(60)?,
|
||||
"h" | "heure" | "heures" => value.checked_mul(3_600)?,
|
||||
"j" | "d" | "jour" | "jours" => value.checked_mul(86_400)?,
|
||||
"w" | "sem" | "semaine" | "semaines" => value.checked_mul(604_800)?,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(seconds.max(1))
|
||||
}
|
||||
|
||||
pub fn format_duration(mut seconds: i64) -> String {
|
||||
seconds = seconds.max(1);
|
||||
let days = seconds / 86_400;
|
||||
seconds %= 86_400;
|
||||
let hours = seconds / 3_600;
|
||||
seconds %= 3_600;
|
||||
let minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
|
||||
let mut out = Vec::new();
|
||||
if days > 0 {
|
||||
out.push(format!("{}j", days));
|
||||
}
|
||||
if hours > 0 {
|
||||
out.push(format!("{}h", hours));
|
||||
}
|
||||
if minutes > 0 {
|
||||
out.push(format!("{}m", minutes));
|
||||
}
|
||||
if seconds > 0 || out.is_empty() {
|
||||
out.push(format!("{}s", seconds));
|
||||
}
|
||||
|
||||
out.join(" ")
|
||||
}
|
||||
|
||||
pub fn parse_rate_limit(input: &str) -> Option<(i32, i32)> {
|
||||
let mut parts = input.splitn(2, '/');
|
||||
let limit = parts.next()?.trim().parse::<i32>().ok()?.max(1);
|
||||
let duration = parse_duration_to_seconds(parts.next()?.trim())?;
|
||||
if duration > i32::MAX as i64 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((limit, duration as i32))
|
||||
}
|
||||
|
||||
pub fn parse_trigger(input: &str) -> Option<&'static str> {
|
||||
match input.trim().to_lowercase().as_str() {
|
||||
"spam" | "antispam" => Some("spam"),
|
||||
"link" | "antilink" => Some("link"),
|
||||
"massmention" | "antimassmention" | "mention" | "mentions" => Some("massmention"),
|
||||
"badword" | "badwords" | "mauvaismot" | "motinterdit" => Some("badword"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_profile(input: Option<&str>) -> Option<&'static str> {
|
||||
let raw = input?.trim().to_lowercase();
|
||||
match raw.as_str() {
|
||||
"ancien" | "old" => Some("old"),
|
||||
"nouveau" | "new" => Some("new"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_sanction(input: &str) -> Option<&'static str> {
|
||||
match input.trim().to_lowercase().as_str() {
|
||||
"warn" | "avert" => Some("warn"),
|
||||
"mute" | "timeout" => Some("mute"),
|
||||
"kick" => Some("kick"),
|
||||
"ban" => Some("ban"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_channel_override(global_enabled: bool, override_mode: Option<&str>) -> bool {
|
||||
match override_mode {
|
||||
Some(mode) if mode.eq_ignore_ascii_case("allow") => false,
|
||||
Some(mode) if mode.eq_ignore_ascii_case("deny") => true,
|
||||
_ => global_enabled,
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_invite_link(content: &str) -> bool {
|
||||
let lower = content.to_lowercase();
|
||||
lower.contains("discord.gg/")
|
||||
|| lower.contains("discord.com/invite/")
|
||||
|| lower.contains("discordapp.com/invite/")
|
||||
}
|
||||
|
||||
fn contains_any_link(content: &str) -> bool {
|
||||
let lower = content.to_lowercase();
|
||||
lower.contains("http://")
|
||||
|| lower.contains("https://")
|
||||
|| lower.contains("www.")
|
||||
|| lower.contains("discord.gg/")
|
||||
}
|
||||
|
||||
fn spam_hit(bot_id: u64, guild_id: u64, user_id: u64, limit: i32, window_seconds: i32) -> bool {
|
||||
let lock = SPAM_TRACKER.get_or_init(|| Mutex::new(HashMap::new()));
|
||||
let mut tracker = lock.lock().expect("spam tracker lock poisoned");
|
||||
|
||||
let key = (bot_id, guild_id, user_id);
|
||||
let now = Instant::now();
|
||||
let window = Duration::from_secs(window_seconds.max(1) as u64);
|
||||
let queue = tracker.entry(key).or_insert_with(VecDeque::new);
|
||||
|
||||
queue.push_back(now);
|
||||
|
||||
while let Some(oldest) = queue.front() {
|
||||
if now.duration_since(*oldest) > window {
|
||||
let _ = queue.pop_front();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
queue.len() > limit.max(1) as usize
|
||||
}
|
||||
|
||||
async fn user_profile(
|
||||
ctx: &Context,
|
||||
pool: &sqlx::PgPool,
|
||||
bot_id: i64,
|
||||
guild_id: GuildId,
|
||||
user_id: UserId,
|
||||
) -> &'static str {
|
||||
let Ok(old_settings) =
|
||||
db::get_or_create_old_member_settings(pool, bot_id, guild_id.get() as i64).await
|
||||
else {
|
||||
return "new";
|
||||
};
|
||||
|
||||
if !old_settings.enabled {
|
||||
return "new";
|
||||
}
|
||||
|
||||
let Some(role_id_raw) = old_settings.role_id else {
|
||||
return "new";
|
||||
};
|
||||
|
||||
let Ok(member) = guild_id.member(&ctx.http, user_id).await else {
|
||||
return "new";
|
||||
};
|
||||
|
||||
if member
|
||||
.roles
|
||||
.iter()
|
||||
.any(|role_id| role_id.get() as i64 == role_id_raw)
|
||||
{
|
||||
"old"
|
||||
} else {
|
||||
"new"
|
||||
}
|
||||
}
|
||||
|
||||
async fn execute_rule(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
user_id: UserId,
|
||||
rule: &PunishRule,
|
||||
settings: &ModerationSettings,
|
||||
) -> String {
|
||||
let sanction = rule.sanction.to_lowercase();
|
||||
let bot_user_id = ctx.cache.current_user().id;
|
||||
|
||||
if sanction == "warn" {
|
||||
add_sanction(
|
||||
ctx,
|
||||
guild_id,
|
||||
user_id,
|
||||
bot_user_id,
|
||||
"warn",
|
||||
"AutoMod: seuil de strikes atteint.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return "warn".to_string();
|
||||
}
|
||||
|
||||
if sanction == "mute" || sanction == "timeout" {
|
||||
let duration = rule
|
||||
.sanction_seconds
|
||||
.unwrap_or(3_600)
|
||||
.clamp(1, 28 * 24 * 3_600);
|
||||
let expires = Some(Utc::now() + chrono::Duration::seconds(duration));
|
||||
let _ = handle_timeout(ctx, guild_id, &[user_id], expires).await;
|
||||
add_sanction(
|
||||
ctx,
|
||||
guild_id,
|
||||
user_id,
|
||||
bot_user_id,
|
||||
"tempmute",
|
||||
"AutoMod: seuil de strikes atteint.",
|
||||
None,
|
||||
expires,
|
||||
)
|
||||
.await;
|
||||
if settings.use_timeout {
|
||||
return format!("timeout {}", format_duration(duration));
|
||||
}
|
||||
return format!("mute role {}", format_duration(duration));
|
||||
}
|
||||
|
||||
if sanction == "kick" {
|
||||
let result = guild_id
|
||||
.kick_with_reason(&ctx.http, user_id, "AutoMod: seuil de strikes atteint")
|
||||
.await;
|
||||
|
||||
if result.is_ok() {
|
||||
add_sanction(
|
||||
ctx,
|
||||
guild_id,
|
||||
user_id,
|
||||
bot_user_id,
|
||||
"kick",
|
||||
"AutoMod: seuil de strikes atteint.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return "kick".to_string();
|
||||
}
|
||||
|
||||
return "kick (echec)".to_string();
|
||||
}
|
||||
|
||||
if sanction == "ban" {
|
||||
let result = guild_id
|
||||
.ban_with_reason(&ctx.http, user_id, 0, "AutoMod: seuil de strikes atteint")
|
||||
.await;
|
||||
|
||||
if result.is_ok() {
|
||||
add_sanction(
|
||||
ctx,
|
||||
guild_id,
|
||||
user_id,
|
||||
bot_user_id,
|
||||
"ban",
|
||||
"AutoMod: seuil de strikes atteint.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return "ban".to_string();
|
||||
}
|
||||
|
||||
return "ban (echec)".to_string();
|
||||
}
|
||||
|
||||
"aucune".to_string()
|
||||
}
|
||||
|
||||
async fn apply_violation(
|
||||
ctx: &Context,
|
||||
msg: &Message,
|
||||
pool: &sqlx::PgPool,
|
||||
settings: &ModerationSettings,
|
||||
trigger: &str,
|
||||
reason: &str,
|
||||
) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let _ = msg.delete(&ctx.http).await;
|
||||
|
||||
let bot_id = settings.bot_id;
|
||||
let guild_id_raw = settings.guild_id;
|
||||
let user_id = msg.author.id;
|
||||
let profile = user_profile(ctx, pool, bot_id, guild_id, user_id).await;
|
||||
|
||||
let strikes = db::get_strike_rule(pool, bot_id, guild_id_raw, trigger, profile)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(1)
|
||||
.max(0);
|
||||
|
||||
if strikes > 0 {
|
||||
let _ = db::add_member_strike_event(
|
||||
pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
user_id.get() as i64,
|
||||
trigger,
|
||||
strikes,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
let _ = ensure_default_punish_rules(pool, bot_id, guild_id_raw).await;
|
||||
let rules = db::list_punish_rules(pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut action = String::from("aucune");
|
||||
for rule in rules.iter().rev() {
|
||||
let Ok(total) = count_member_strikes_in_window(
|
||||
pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
user_id.get() as i64,
|
||||
rule.window_seconds,
|
||||
)
|
||||
.await
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if total < rule.threshold as i64 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let recent_trigger =
|
||||
get_last_punish_triggered_at(pool, bot_id, guild_id_raw, user_id.get() as i64, rule.id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|at| Utc::now() - at < chrono::Duration::seconds(rule.window_seconds))
|
||||
.unwrap_or(false);
|
||||
|
||||
if recent_trigger {
|
||||
continue;
|
||||
}
|
||||
|
||||
action = execute_rule(ctx, guild_id, user_id, rule, settings).await;
|
||||
let _ = upsert_last_punish_triggered_at(
|
||||
pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
user_id.get() as i64,
|
||||
rule.id,
|
||||
)
|
||||
.await;
|
||||
break;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("AutoMod")
|
||||
.description(format!(
|
||||
"{}\nMembre: <@{}>\nTrigger: `{}` · Profil: `{}` · Strikes: `+{}`\nAction: `{}`",
|
||||
reason,
|
||||
user_id.get(),
|
||||
trigger,
|
||||
profile,
|
||||
strikes,
|
||||
action
|
||||
))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn enforce_automod_message(ctx: &Context, msg: &Message) -> bool {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let settings =
|
||||
match db::get_or_create_moderation_settings(&pool, bot_id, guild_id.get() as i64).await {
|
||||
Ok(settings) => settings,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
let channel_id = msg.channel_id.get() as i64;
|
||||
let spam_override = db::get_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
channel_id,
|
||||
"spam",
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
let link_override = db::get_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
channel_id,
|
||||
"link",
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let antispam_enabled =
|
||||
apply_channel_override(settings.antispam_enabled, spam_override.as_deref());
|
||||
let antilink_enabled =
|
||||
apply_channel_override(settings.antilink_enabled, link_override.as_deref());
|
||||
|
||||
if settings.badwords_enabled {
|
||||
let content = msg.content.to_lowercase();
|
||||
let badwords = db::list_badwords(&pool, bot_id, guild_id.get() as i64)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if badwords
|
||||
.iter()
|
||||
.any(|word| !word.is_empty() && content.contains(word))
|
||||
{
|
||||
apply_violation(
|
||||
ctx,
|
||||
msg,
|
||||
&pool,
|
||||
&settings,
|
||||
"badword",
|
||||
"Message supprime: mot interdit detecte.",
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if settings.antimassmention_enabled {
|
||||
let mention_count = msg.mentions.len() + msg.mention_roles.len();
|
||||
if mention_count >= settings.antimassmention_limit.max(1) as usize {
|
||||
apply_violation(
|
||||
ctx,
|
||||
msg,
|
||||
&pool,
|
||||
&settings,
|
||||
"massmention",
|
||||
"Message supprime: spam de mentions detecte.",
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if antilink_enabled {
|
||||
let link_hit = if settings.antilink_mode.eq_ignore_ascii_case("all") {
|
||||
contains_any_link(&msg.content)
|
||||
} else {
|
||||
contains_invite_link(&msg.content)
|
||||
};
|
||||
|
||||
if link_hit {
|
||||
apply_violation(
|
||||
ctx,
|
||||
msg,
|
||||
&pool,
|
||||
&settings,
|
||||
"link",
|
||||
"Message supprime: lien interdit detecte.",
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if antispam_enabled {
|
||||
let hit = spam_hit(
|
||||
ctx.cache.current_user().id.get(),
|
||||
guild_id.get(),
|
||||
msg.author.id.get(),
|
||||
settings.antispam_limit.max(1),
|
||||
settings.antispam_window_seconds.max(1),
|
||||
);
|
||||
|
||||
if hit {
|
||||
apply_violation(
|
||||
ctx,
|
||||
msg,
|
||||
&pool,
|
||||
&settings,
|
||||
"spam",
|
||||
"Message supprime: spam detecte.",
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn public_command_allowed(
|
||||
ctx: &Context,
|
||||
msg: &Message,
|
||||
command_key: &str,
|
||||
required_permission: u8,
|
||||
) -> bool {
|
||||
if permissions::is_owner_user(ctx, msg.author.id).await {
|
||||
return true;
|
||||
}
|
||||
|
||||
if required_permission > 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let settings =
|
||||
match db::get_or_create_moderation_settings(&pool, bot_id, guild_id.get() as i64).await {
|
||||
Ok(settings) => settings,
|
||||
Err(_) => return true,
|
||||
};
|
||||
|
||||
let override_mode = db::get_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
msg.channel_id.get() as i64,
|
||||
"public",
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let allowed = match override_mode.as_deref() {
|
||||
Some(mode) if mode.eq_ignore_ascii_case("allow") => true,
|
||||
Some(mode) if mode.eq_ignore_ascii_case("deny") => false,
|
||||
_ => settings.public_commands_enabled,
|
||||
};
|
||||
if allowed {
|
||||
return true;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Commandes publiques desactivees")
|
||||
.description(format!(
|
||||
"La commande `{}` est desactivee dans ce salon.",
|
||||
command_key.replace('_', "")
|
||||
))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
false
|
||||
}
|
||||
@@ -6,6 +6,29 @@ use crate::commands::common::send_embed;
|
||||
use crate::commands::perms_helpers::{ensure_owner, get_pool, normalize_command_name};
|
||||
use crate::db::{reset_command_permissions, set_command_permission};
|
||||
|
||||
pub async fn handle_changereset(ctx: &Context, msg: &Message) {
|
||||
if !ensure_owner(ctx, msg).await {
|
||||
return;
|
||||
}
|
||||
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(pool) = get_pool(ctx).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let removed = reset_command_permissions(&pool, bot_id).await.unwrap_or(0);
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Permissions reinitialisees")
|
||||
.description(format!("Overrides supprimes: {}", removed))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_change(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if !ensure_owner(ctx, msg).await {
|
||||
return;
|
||||
@@ -21,20 +44,6 @@ pub async fn handle_change(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
};
|
||||
|
||||
if args
|
||||
.first()
|
||||
.map(|s| s.eq_ignore_ascii_case("reset"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let removed = reset_command_permissions(&pool, bot_id).await.unwrap_or(0);
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Permissions reinitialisees")
|
||||
.description(format!("Overrides supprimes: {}", removed))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args.len() < 2 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
@@ -79,8 +88,8 @@ impl crate::commands::command_contract::CommandSpec for ChangeCommand {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "change",
|
||||
category: "botconfig",
|
||||
params: "<commande> <niveau 0-9> | reset",
|
||||
description: "Definit le niveau ACL requis pour une commande ou reinitialise les overrides.",
|
||||
params: "<commande> <niveau 0-9>",
|
||||
description: "Definit le niveau ACL requis pour une commande cible.",
|
||||
examples: &["+change", "+ce", "+help change"],
|
||||
default_aliases: &["chg"],
|
||||
allow_in_dm: false,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_changereset_command(ctx: &Context, msg: &Message, _args: &[&str]) {
|
||||
crate::commands::change::handle_changereset(ctx, msg).await;
|
||||
}
|
||||
|
||||
pub struct ChangeresetCommand;
|
||||
pub static COMMAND_DESCRIPTOR: ChangeresetCommand = ChangeresetCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for ChangeresetCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "changereset",
|
||||
category: "botconfig",
|
||||
params: "aucun",
|
||||
description: "Reinitialise tous les overrides ACL des commandes.",
|
||||
examples: &["+changereset", "+help changereset"],
|
||||
default_aliases: &["chgr"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 9,
|
||||
}
|
||||
}
|
||||
}
|
||||
+192
-237
@@ -8,12 +8,12 @@ use crate::commands::perms_helpers::{
|
||||
};
|
||||
use crate::db::{grant_command_access, grant_perm_level};
|
||||
|
||||
async fn handle_set_perm(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
pub async fn handle_setperm(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if !ensure_owner(ctx, msg).await {
|
||||
return;
|
||||
}
|
||||
|
||||
if args.len() < 3 || !args[0].eq_ignore_ascii_case("perm") {
|
||||
if args.len() < 2 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+setperm <permission/commande> <role/membre>`")
|
||||
@@ -22,7 +22,7 @@ async fn handle_set_perm(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some((scope_type, scope_id)) = parse_user_or_role(args[2]) else {
|
||||
let Some((scope_type, scope_id)) = parse_user_or_role(args[1]) else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Role/membre invalide.")
|
||||
@@ -41,7 +41,7 @@ async fn handle_set_perm(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Ok(level) = args[1].parse::<u8>() {
|
||||
if let Ok(level) = args[0].parse::<u8>() {
|
||||
if level > 9 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
@@ -65,7 +65,7 @@ async fn handle_set_perm(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let command = normalize_command_name(args[1]);
|
||||
let command = normalize_command_name(args[0]);
|
||||
let _ = grant_command_access(&pool, bot_id, scope_type, scope_id, &command).await;
|
||||
|
||||
let who = if scope_type == "role" {
|
||||
@@ -81,254 +81,209 @@ async fn handle_set_perm(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_set(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if args
|
||||
.first()
|
||||
.map(|a| a.eq_ignore_ascii_case("perm"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
handle_set_perm(ctx, msg, args).await;
|
||||
return;
|
||||
}
|
||||
|
||||
pub async fn handle_setname(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if args.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+set name|pic|banner|profil ...`")
|
||||
.description("Usage: `+setname <nom>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let sub = args[0].to_lowercase();
|
||||
|
||||
match sub.as_str() {
|
||||
"name" => {
|
||||
if args.len() < 2 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+set name <nom>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let name = args[1..].join(" ");
|
||||
let mut me = match ctx.http.get_current_user().await {
|
||||
Ok(user) => user,
|
||||
Err(err) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de charger le profil bot: {}", err))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let result = me
|
||||
.edit(&ctx.http, EditProfile::new().username(name.clone()))
|
||||
.await;
|
||||
let embed = match result {
|
||||
Ok(_) => CreateEmbed::new()
|
||||
.title("Profil mis à jour")
|
||||
.description(format!("Nom défini sur: {}", name))
|
||||
.color(0x57F287),
|
||||
Err(err) => CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de modifier le nom: {}", err))
|
||||
.color(0xED4245),
|
||||
};
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
"pic" => {
|
||||
if args.len() < 2 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+set pic <lien_image>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(attachment) = CreateAttachment::url(&ctx.http, args[1]).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Impossible de télécharger l'image.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let mut me = match ctx.http.get_current_user().await {
|
||||
Ok(user) => user,
|
||||
Err(err) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de charger le profil bot: {}", err))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let result = me
|
||||
.edit(&ctx.http, EditProfile::new().avatar(&attachment))
|
||||
.await;
|
||||
let embed = match result {
|
||||
Ok(_) => CreateEmbed::new()
|
||||
.title("Profil mis à jour")
|
||||
.description("Photo de profil modifiée.")
|
||||
.color(0x57F287),
|
||||
Err(err) => CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de modifier la photo: {}", err))
|
||||
.color(0xED4245),
|
||||
};
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
"banner" => {
|
||||
if args.len() < 2 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+set banner <lien_image>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(attachment) = CreateAttachment::url(&ctx.http, args[1]).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Impossible de télécharger l'image.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let mut me = match ctx.http.get_current_user().await {
|
||||
Ok(user) => user,
|
||||
Err(err) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de charger le profil bot: {}", err))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let result = me
|
||||
.edit(&ctx.http, EditProfile::new().banner(&attachment))
|
||||
.await;
|
||||
let embed = match result {
|
||||
Ok(_) => CreateEmbed::new()
|
||||
.title("Profil mis à jour")
|
||||
.description("Bannière modifiée.")
|
||||
.color(0x57F287),
|
||||
Err(err) => CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de modifier la bannière: {}", err))
|
||||
.color(0xED4245),
|
||||
};
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
"profil" => {
|
||||
let raw = args[1..].join(" ");
|
||||
let parts: Vec<&str> = raw.split(";;").map(|s| s.trim()).collect();
|
||||
|
||||
if parts.len() != 3 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+set profil <nom> ;; <lien_pic> ;; <lien_banner>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let mut builder = EditProfile::new();
|
||||
|
||||
if !parts[0].is_empty() {
|
||||
builder = builder.username(parts[0].to_string());
|
||||
}
|
||||
|
||||
if !parts[1].is_empty() {
|
||||
match CreateAttachment::url(&ctx.http, parts[1]).await {
|
||||
Ok(avatar) => builder = builder.avatar(&avatar),
|
||||
Err(_) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Image avatar invalide dans `+set profil`. ")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !parts[2].is_empty() {
|
||||
match CreateAttachment::url(&ctx.http, parts[2]).await {
|
||||
Ok(banner) => builder = builder.banner(&banner),
|
||||
Err(_) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Image bannière invalide dans `+set profil`. ")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut me = match ctx.http.get_current_user().await {
|
||||
Ok(user) => user,
|
||||
Err(err) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de charger le profil bot: {}", err))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let result = me.edit(&ctx.http, builder).await;
|
||||
let embed = match result {
|
||||
Ok(_) => CreateEmbed::new()
|
||||
.title("Profil mis à jour")
|
||||
.description("Nom, avatar et bannière traités.")
|
||||
.color(0x57F287),
|
||||
Err(err) => CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de modifier le profil: {}", err))
|
||||
.color(0xED4245),
|
||||
};
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
_ => {
|
||||
let name = args.join(" ");
|
||||
let mut me = match ctx.http.get_current_user().await {
|
||||
Ok(user) => user,
|
||||
Err(err) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Sous-commande inconnue. Utilise `name`, `pic`, `banner` ou `profil`.")
|
||||
.description(format!("Impossible de charger le profil bot: {}", err))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let result = me
|
||||
.edit(&ctx.http, EditProfile::new().username(name.clone()))
|
||||
.await;
|
||||
let embed = match result {
|
||||
Ok(_) => CreateEmbed::new()
|
||||
.title("Profil mis à jour")
|
||||
.description(format!("Nom défini sur: {}", name))
|
||||
.color(0x57F287),
|
||||
Err(err) => CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de modifier le nom: {}", err))
|
||||
.color(0xED4245),
|
||||
};
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub struct SetCommand;
|
||||
pub static COMMAND_DESCRIPTOR: SetCommand = SetCommand;
|
||||
pub async fn handle_setpic(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if args.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+setpic <lien_image>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for SetCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "set",
|
||||
category: "botconfig",
|
||||
params: "name <nom> | pic <url> | banner <url> | profil <nom> ;; <url_pic> ;; <url_banner> | perm ...",
|
||||
description: "Modifie le nom, lavatar, la banniere ou des options avancees via les sous commandes.",
|
||||
examples: &["+set", "+st", "+help set"],
|
||||
default_aliases: &["cfg"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 9,
|
||||
let Ok(attachment) = CreateAttachment::url(&ctx.http, args[0]).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Impossible de télécharger l'image.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let mut me = match ctx.http.get_current_user().await {
|
||||
Ok(user) => user,
|
||||
Err(err) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de charger le profil bot: {}", err))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let result = me
|
||||
.edit(&ctx.http, EditProfile::new().avatar(&attachment))
|
||||
.await;
|
||||
let embed = match result {
|
||||
Ok(_) => CreateEmbed::new()
|
||||
.title("Profil mis à jour")
|
||||
.description("Photo de profil modifiée.")
|
||||
.color(0x57F287),
|
||||
Err(err) => CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de modifier la photo: {}", err))
|
||||
.color(0xED4245),
|
||||
};
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_setbanner(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if args.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+setbanner <lien_image>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(attachment) = CreateAttachment::url(&ctx.http, args[0]).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Impossible de télécharger l'image.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let mut me = match ctx.http.get_current_user().await {
|
||||
Ok(user) => user,
|
||||
Err(err) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de charger le profil bot: {}", err))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let result = me
|
||||
.edit(&ctx.http, EditProfile::new().banner(&attachment))
|
||||
.await;
|
||||
let embed = match result {
|
||||
Ok(_) => CreateEmbed::new()
|
||||
.title("Profil mis à jour")
|
||||
.description("Bannière modifiée.")
|
||||
.color(0x57F287),
|
||||
Err(err) => CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de modifier la bannière: {}", err))
|
||||
.color(0xED4245),
|
||||
};
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_setprofil(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let raw = args.join(" ");
|
||||
let parts: Vec<&str> = raw.split(";;").map(|s| s.trim()).collect();
|
||||
|
||||
if parts.len() != 3 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+setprofil <nom> ;; <lien_pic> ;; <lien_banner>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let mut builder = EditProfile::new();
|
||||
|
||||
if !parts[0].is_empty() {
|
||||
builder = builder.username(parts[0].to_string());
|
||||
}
|
||||
|
||||
if !parts[1].is_empty() {
|
||||
match CreateAttachment::url(&ctx.http, parts[1]).await {
|
||||
Ok(avatar) => builder = builder.avatar(&avatar),
|
||||
Err(_) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Image avatar invalide dans `+setprofil`.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !parts[2].is_empty() {
|
||||
match CreateAttachment::url(&ctx.http, parts[2]).await {
|
||||
Ok(banner) => builder = builder.banner(&banner),
|
||||
Err(_) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Image bannière invalide dans `+setprofil`.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut me = match ctx.http.get_current_user().await {
|
||||
Ok(user) => user,
|
||||
Err(err) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de charger le profil bot: {}", err))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let result = me.edit(&ctx.http, builder).await;
|
||||
let embed = match result {
|
||||
Ok(_) => CreateEmbed::new()
|
||||
.title("Profil mis à jour")
|
||||
.description("Nom, avatar et bannière traités.")
|
||||
.color(0x57F287),
|
||||
Err(err) => CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de modifier le profil: {}", err))
|
||||
.color(0xED4245),
|
||||
};
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_setbanner_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::set::handle_setbanner(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct SetbannerCommand;
|
||||
pub static COMMAND_DESCRIPTOR: SetbannerCommand = SetbannerCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for SetbannerCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "setbanner",
|
||||
category: "botconfig",
|
||||
params: "<url>",
|
||||
description: "Modifie la banniere du bot.",
|
||||
examples: &["+setbanner https://exemple/banner.png", "+help setbanner"],
|
||||
default_aliases: &["stbn"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 9,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_setname_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::set::handle_setname(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct SetnameCommand;
|
||||
pub static COMMAND_DESCRIPTOR: SetnameCommand = SetnameCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for SetnameCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "setname",
|
||||
category: "botconfig",
|
||||
params: "<nom>",
|
||||
description: "Modifie le nom du bot.",
|
||||
examples: &["+setname MonBot", "+help setname"],
|
||||
default_aliases: &["stn"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 9,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_setperm_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::set::handle_setperm(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct SetpermCommand;
|
||||
pub static COMMAND_DESCRIPTOR: SetpermCommand = SetpermCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for SetpermCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "setperm",
|
||||
category: "botconfig",
|
||||
params: "<permission/commande> <role/membre>",
|
||||
description: "Attribue un niveau ACL ou un acces commande a un role ou membre.",
|
||||
examples: &["+setperm 6 @Moderateur", "+setperm mute @Role", "+help setperm"],
|
||||
default_aliases: &["stp"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 9,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_setpic_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::set::handle_setpic(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct SetpicCommand;
|
||||
pub static COMMAND_DESCRIPTOR: SetpicCommand = SetpicCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for SetpicCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "setpic",
|
||||
category: "botconfig",
|
||||
params: "<url>",
|
||||
description: "Modifie l'avatar du bot.",
|
||||
examples: &["+setpic https://exemple/image.png", "+help setpic"],
|
||||
default_aliases: &["stpic"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 9,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_setprofil_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::set::handle_setprofil(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct SetprofilCommand;
|
||||
pub static COMMAND_DESCRIPTOR: SetprofilCommand = SetprofilCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for SetprofilCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "setprofil",
|
||||
category: "botconfig",
|
||||
params: "<nom> ;; <url_pic> ;; <url_banner>",
|
||||
description: "Met a jour en une commande le nom, l'avatar et la banniere du bot.",
|
||||
examples: &[
|
||||
"+setprofil Shadow ;; https://img/a.png ;; https://img/b.png",
|
||||
"+help setprofil",
|
||||
],
|
||||
default_aliases: &["stpr"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 9,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
pub fn parse_color(value: &str) -> Option<u32> {
|
||||
let v = value.trim().to_lowercase();
|
||||
match v.as_str() {
|
||||
"red" | "rouge" => Some(0xED4245),
|
||||
"green" | "vert" => Some(0x57F287),
|
||||
"blue" | "bleu" => Some(0x5865F2),
|
||||
"yellow" | "jaune" => Some(0xFEE75C),
|
||||
"orange" => Some(0xFAA61A),
|
||||
"purple" | "violet" => Some(0x9B59B6),
|
||||
"pink" | "rose" => Some(0xEB459E),
|
||||
"white" | "blanc" => Some(0xFFFFFF),
|
||||
"black" | "noir" => Some(0x000000),
|
||||
_ => {
|
||||
let hex = v.trim_start_matches('#').trim_start_matches("0x");
|
||||
u32::from_str_radix(hex, 16).ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::activity::{RotatingActivityKind, parse_status, start_rotation};
|
||||
use crate::db::DbPoolKey;
|
||||
|
||||
pub async fn restore_presence_from_db(ctx: &Context) {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
|
||||
let pool = {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
};
|
||||
|
||||
let Some(pool) = pool else {
|
||||
return;
|
||||
};
|
||||
|
||||
let status = match crate::db::get_bot_status(&pool, bot_id).await {
|
||||
Ok(Some(saved)) => parse_status(&saved),
|
||||
_ => OnlineStatus::Online,
|
||||
};
|
||||
|
||||
ctx.set_presence(None, status);
|
||||
|
||||
let activity_row = crate::db::get_bot_activity(&pool, bot_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
if let Some((kind_raw, messages_raw)) = activity_row {
|
||||
let Some(kind) = RotatingActivityKind::from_db(&kind_raw) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let messages: Vec<String> = messages_raw
|
||||
.split('\n')
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
if !messages.is_empty() {
|
||||
start_rotation(ctx, kind, messages, status).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CommandMetadata {
|
||||
pub name: &'static str,
|
||||
pub category: &'static str,
|
||||
pub allow_in_dm: bool,
|
||||
pub default_permission: u8,
|
||||
pub params: &'static str,
|
||||
pub description: &'static str,
|
||||
pub examples: &'static [&'static str],
|
||||
pub default_aliases: &'static [&'static str],
|
||||
}
|
||||
|
||||
pub trait CommandSpec: Send + Sync {
|
||||
fn metadata(&self) -> CommandMetadata;
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
use serenity::builder::{CreateEmbed, CreateMessage};
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::db::{DbPoolKey, get_bot_theme};
|
||||
|
||||
pub fn parse_limit(args: &[&str], default: usize, max: usize) -> usize {
|
||||
args.iter()
|
||||
.find_map(|arg| arg.parse::<usize>().ok())
|
||||
.map(|value| value.clamp(1, max))
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
pub fn has_flag(args: &[&str], names: &[&str]) -> bool {
|
||||
args.iter()
|
||||
.any(|arg| names.iter().any(|name| arg.eq_ignore_ascii_case(name)))
|
||||
}
|
||||
|
||||
pub fn truncate_text(input: &str, max_len: usize) -> String {
|
||||
if input.chars().count() <= max_len {
|
||||
return input.to_string();
|
||||
}
|
||||
|
||||
let mut out = input
|
||||
.chars()
|
||||
.take(max_len.saturating_sub(1))
|
||||
.collect::<String>();
|
||||
out.push('…');
|
||||
out
|
||||
}
|
||||
|
||||
pub fn add_list_fields(mut embed: CreateEmbed, lines: &[String], base_name: &str) -> CreateEmbed {
|
||||
if lines.is_empty() {
|
||||
return embed.field(base_name, "Aucun résultat.", false);
|
||||
}
|
||||
|
||||
let max_fields = 3;
|
||||
let chunk_size = 12;
|
||||
|
||||
for (index, chunk) in lines.chunks(chunk_size).take(max_fields).enumerate() {
|
||||
let field_name = if index == 0 {
|
||||
base_name.to_string()
|
||||
} else {
|
||||
format!("{} (suite {})", base_name, index + 1)
|
||||
};
|
||||
|
||||
let value = truncate_text(&chunk.join("\n"), 1024);
|
||||
embed = embed.field(field_name, value, false);
|
||||
}
|
||||
|
||||
let shown = (chunk_size * max_fields).min(lines.len());
|
||||
if lines.len() > shown {
|
||||
embed = embed.field(
|
||||
"Affichage",
|
||||
format!("{} éléments affichés sur {}.", shown, lines.len()),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
embed
|
||||
}
|
||||
|
||||
pub fn mention_user(user_id: UserId) -> String {
|
||||
format!("<@{}>", user_id.get())
|
||||
}
|
||||
|
||||
pub fn discord_ts(timestamp: Timestamp, style: &str) -> String {
|
||||
format!("<t:{}:{}>", timestamp.unix_timestamp(), style)
|
||||
}
|
||||
|
||||
pub async fn send_embed(ctx: &Context, msg: &Message, embed: CreateEmbed) {
|
||||
let color = theme_color(ctx).await;
|
||||
let embed = embed.color(color);
|
||||
|
||||
let _ = msg
|
||||
.channel_id
|
||||
.send_message(&ctx.http, CreateMessage::new().embed(embed))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn theme_color(ctx: &Context) -> u32 {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let pool = {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
};
|
||||
|
||||
if let Some(pool) = pool {
|
||||
if let Ok(Some(color)) = get_bot_theme(&pool, bot_id).await {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
0xFF0000
|
||||
}
|
||||
|
||||
pub fn parse_role(guild: &PartialGuild, input: &str) -> Option<Role> {
|
||||
// Essayer de parser comme mention <@&id>
|
||||
if let Ok(id) = input
|
||||
.trim_start_matches("<@&")
|
||||
.trim_end_matches('>')
|
||||
.parse::<u64>()
|
||||
{
|
||||
if let Some(role) = guild.roles.get(&RoleId::new(id)) {
|
||||
return Some(role.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Essayer de parser comme ID brut
|
||||
if let Ok(id) = input.parse::<u64>() {
|
||||
if let Some(role) = guild.roles.get(&RoleId::new(id)) {
|
||||
return Some(role.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Chercher par nom (case-insensitive)
|
||||
let search = input.to_lowercase();
|
||||
guild
|
||||
.roles
|
||||
.values()
|
||||
.find(|r| r.name.to_lowercase().contains(&search))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn parse_channel_id(input: &str) -> Option<ChannelId> {
|
||||
// Essayer de parser comme mention <#id>
|
||||
if let Ok(id) = input
|
||||
.trim_start_matches("<#")
|
||||
.trim_end_matches('>')
|
||||
.parse::<u64>()
|
||||
{
|
||||
return Some(ChannelId::new(id));
|
||||
}
|
||||
|
||||
// Essayer de parser comme ID brut
|
||||
if let Ok(id) = input.parse::<u64>() {
|
||||
return Some(ChannelId::new(id));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
+22
-22
@@ -15,20 +15,7 @@ pub async fn handle_join(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
|
||||
if args.is_empty() || !args[0].eq_ignore_ascii_case("settings") {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("join settings")
|
||||
.description("Usage: +join settings [on/off] [salon] [message...]")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args.len() == 1 {
|
||||
if args.is_empty() {
|
||||
let row = sqlx::query_as::<_, (bool, Option<i64>, Option<String>)>(
|
||||
r#"
|
||||
SELECT enabled, channel_id, custom_message
|
||||
@@ -70,14 +57,27 @@ pub async fn handle_join(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let action = args[1].to_lowercase();
|
||||
let action = args[0].to_lowercase();
|
||||
if action != "on" && action != "off" {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("join settings")
|
||||
.description("Usage: +joinsettings [on/off] [salon] [message...]")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let enabled = action == "on";
|
||||
let channel = if enabled {
|
||||
parse_target_channel(msg, args, 2)
|
||||
parse_target_channel(msg, args, 1)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let message_start = if enabled { 3 } else { 2 };
|
||||
let message_start = if enabled { 2 } else { 1 };
|
||||
let custom_message = if args.len() > message_start {
|
||||
Some(args[message_start..].join(" "))
|
||||
} else {
|
||||
@@ -125,15 +125,15 @@ pub static COMMAND_DESCRIPTOR: JoinCommand = JoinCommand;
|
||||
impl crate::commands::command_contract::CommandSpec for JoinCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "join",
|
||||
name: "joinsettings",
|
||||
category: "config",
|
||||
params: "settings [on/off] [salon] [message]",
|
||||
params: "[on/off] [salon] [message]",
|
||||
description: "Permet de configurer les actions quand un membre rejoint.",
|
||||
examples: &[
|
||||
"+join settings",
|
||||
"+join settings on #welcome Bienvenue {user}",
|
||||
"+joinsettings",
|
||||
"+joinsettings on #welcome Bienvenue {user}",
|
||||
],
|
||||
default_aliases: &["jset"],
|
||||
default_aliases: &["jset", "join"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 5,
|
||||
}
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use serenity::builder::CreateEmbed;
|
||||
|
||||
use crate::commands::common::{send_embed, theme_color};
|
||||
use crate::commands::logs_command_helpers::{parse_target_channel, pool};
|
||||
|
||||
pub async fn handle_leave_settings(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
|
||||
if args.is_empty() || !args[0].eq_ignore_ascii_case("settings") {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("leave settings")
|
||||
.description("Usage: +leavesettings [on/off] [salon] [message...]")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args.len() == 1 {
|
||||
let row = sqlx::query_as::<_, (bool, Option<i64>, Option<String>)>(
|
||||
r#"
|
||||
SELECT enabled, channel_id, custom_message
|
||||
FROM bot_join_leave_settings
|
||||
WHERE bot_id = $1 AND guild_id = $2 AND kind = $3
|
||||
LIMIT 1;
|
||||
"#,
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.bind("leave")
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let desc = if let Some((enabled, channel_id, custom_message)) = row {
|
||||
format!(
|
||||
"Etat: {}\nSalon: {}\nMessage: {}",
|
||||
if enabled { "on" } else { "off" },
|
||||
channel_id
|
||||
.map(|id| format!("<#{}>", id))
|
||||
.unwrap_or_else(|| "non defini".to_string()),
|
||||
custom_message.unwrap_or_else(|| "(defaut)".to_string())
|
||||
)
|
||||
} else {
|
||||
"Aucun reglage configure.".to_string()
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("leave settings")
|
||||
.description(desc)
|
||||
.color(theme_color(ctx).await),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let action = args[1].to_lowercase();
|
||||
let enabled = action == "on";
|
||||
let channel = if enabled {
|
||||
parse_target_channel(msg, args, 2)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let message_start = if enabled { 3 } else { 2 };
|
||||
let custom_message = if args.len() > message_start {
|
||||
Some(args[message_start..].join(" "))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let _ = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO bot_join_leave_settings (bot_id, guild_id, kind, enabled, channel_id, custom_message)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
ON CONFLICT (bot_id, guild_id, kind)
|
||||
DO UPDATE SET enabled = EXCLUDED.enabled, channel_id = EXCLUDED.channel_id,
|
||||
custom_message = EXCLUDED.custom_message, updated_at = NOW();
|
||||
"#,
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.bind("leave")
|
||||
.bind(enabled)
|
||||
.bind(channel.map(|c| c.get() as i64))
|
||||
.bind(custom_message)
|
||||
.execute(&pool)
|
||||
.await;
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("leave settings")
|
||||
.description(format!(
|
||||
"{} {}",
|
||||
if enabled { "Active" } else { "Desactive" },
|
||||
channel
|
||||
.map(|c| format!("dans <#{}>", c.get()))
|
||||
.unwrap_or_default()
|
||||
))
|
||||
.color(theme_color(ctx).await),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct LeaveSettingsCommand;
|
||||
pub static COMMAND_DESCRIPTOR: LeaveSettingsCommand = LeaveSettingsCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for LeaveSettingsCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "leave_settings",
|
||||
category: "config",
|
||||
params: "settings [on/off] [salon] [message]",
|
||||
description: "Configure les actions a executer quand un membre quitte le serveur.",
|
||||
examples: &[
|
||||
"+leavesettings",
|
||||
"+leavesettings on #logs {user} a quitte",
|
||||
],
|
||||
default_aliases: &["lset"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ pub async fn handle_leave_settings(ctx: &Context, msg: &Message, args: &[&str])
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
|
||||
if args.is_empty() || !args[0].eq_ignore_ascii_case("settings") {
|
||||
if args.is_empty() {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
@@ -71,14 +71,27 @@ pub async fn handle_leave_settings(ctx: &Context, msg: &Message, args: &[&str])
|
||||
return;
|
||||
}
|
||||
|
||||
let action = args[1].to_lowercase();
|
||||
let action = args[0].to_lowercase();
|
||||
if action != "on" && action != "off" {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("leave settings")
|
||||
.description("Usage: +leavesettings [on/off] [salon] [message...]")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let enabled = action == "on";
|
||||
let channel = if enabled {
|
||||
parse_target_channel(msg, args, 2)
|
||||
parse_target_channel(msg, args, 1)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let message_start = if enabled { 3 } else { 2 };
|
||||
let message_start = if enabled { 2 } else { 1 };
|
||||
let custom_message = if args.len() > message_start {
|
||||
Some(args[message_start..].join(" "))
|
||||
} else {
|
||||
@@ -128,7 +141,7 @@ impl crate::commands::command_contract::CommandSpec for LeaveSettingsCommand {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "leavesettings",
|
||||
category: "config",
|
||||
params: "settings [on/off] [salon] [message]",
|
||||
params: "[on/off] [salon] [message]",
|
||||
description: "Configure les actions a executer quand un membre quitte le serveur.",
|
||||
examples: &[
|
||||
"+leavesettings",
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{send_embed, theme_color};
|
||||
use crate::commands::logs_command_helpers::pool;
|
||||
|
||||
pub async fn handle_set_boostembed(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
if args.len() < 2 {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Set BoostEmbed")
|
||||
.description("Usage: +setboostembed <title|description|color> <valeur>")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let field = args[0].to_lowercase();
|
||||
let value = args[1..].join(" ");
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
|
||||
let _ = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO bot_boost_embed (bot_id, guild_id, enabled, title, description, color)
|
||||
VALUES ($1, $2, TRUE, NULL, NULL, NULL)
|
||||
ON CONFLICT (bot_id, guild_id)
|
||||
DO NOTHING;
|
||||
"#,
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.execute(&pool)
|
||||
.await;
|
||||
|
||||
match field.as_str() {
|
||||
"title" => {
|
||||
let _ = sqlx::query(
|
||||
"UPDATE bot_boost_embed SET title = $3, updated_at = NOW() WHERE bot_id = $1 AND guild_id = $2",
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.bind(value)
|
||||
.execute(&pool)
|
||||
.await;
|
||||
}
|
||||
"description" => {
|
||||
let _ = sqlx::query(
|
||||
"UPDATE bot_boost_embed SET description = $3, updated_at = NOW() WHERE bot_id = $1 AND guild_id = $2",
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.bind(value)
|
||||
.execute(&pool)
|
||||
.await;
|
||||
}
|
||||
"color" => {
|
||||
let normalized = value
|
||||
.trim()
|
||||
.trim_start_matches('#')
|
||||
.trim_start_matches("0x");
|
||||
if let Ok(color) = u32::from_str_radix(normalized, 16) {
|
||||
let _ = sqlx::query(
|
||||
"UPDATE bot_boost_embed SET color = $3, updated_at = NOW() WHERE bot_id = $1 AND guild_id = $2",
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.bind(color as i32)
|
||||
.execute(&pool)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Set BoostEmbed")
|
||||
.description("Configuration mise a jour.")
|
||||
.color(theme_color(ctx).await),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct SetBoostembedCommand;
|
||||
pub static COMMAND_DESCRIPTOR: SetBoostembedCommand = SetBoostembedCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for SetBoostembedCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "set_boostembed",
|
||||
category: "config",
|
||||
params: "<title|description|color> <valeur>",
|
||||
description: "Configure le titre, la description et la couleur de l embed boost.",
|
||||
examples: &[
|
||||
"+setboostembed title Merci",
|
||||
"+setboostembed color #FF66CC",
|
||||
],
|
||||
default_aliases: &["sboostembed"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 6,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{send_embed, theme_color};
|
||||
use crate::commands::logs_command_helpers::pool;
|
||||
|
||||
pub async fn handle_set_modlogs(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
|
||||
let row = sqlx::query_as::<_, (String,)>(
|
||||
r#"
|
||||
SELECT modlog_events
|
||||
FROM bot_log_settings
|
||||
WHERE bot_id = $1 AND guild_id = $2
|
||||
LIMIT 1;
|
||||
"#,
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let mut events = row
|
||||
.map(|(s,)| {
|
||||
s.split(',')
|
||||
.map(|v| v.trim().to_lowercase())
|
||||
.filter(|v| !v.is_empty())
|
||||
.collect::<BTreeSet<_>>()
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
[
|
||||
"warn",
|
||||
"mute",
|
||||
"tempmute",
|
||||
"unmute",
|
||||
"cmute",
|
||||
"tempcmute",
|
||||
"uncmute",
|
||||
"kick",
|
||||
"ban",
|
||||
"tempban",
|
||||
"unban",
|
||||
"lock",
|
||||
"unlock",
|
||||
"hide",
|
||||
"unhide",
|
||||
"addrole",
|
||||
"delrole",
|
||||
"derank",
|
||||
"clear",
|
||||
"sanctions",
|
||||
]
|
||||
.into_iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
});
|
||||
|
||||
if args.len() >= 2 {
|
||||
let event = args[0].to_lowercase();
|
||||
let state = args[1].to_lowercase();
|
||||
if state == "on" {
|
||||
events.insert(event);
|
||||
} else if state == "off" {
|
||||
events.remove(&event);
|
||||
}
|
||||
|
||||
let serialized = events.iter().cloned().collect::<Vec<_>>().join(",");
|
||||
let _ = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO bot_log_settings (bot_id, guild_id, modlog_events)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (bot_id, guild_id)
|
||||
DO UPDATE SET modlog_events = EXCLUDED.modlog_events, updated_at = NOW();
|
||||
"#,
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.bind(serialized)
|
||||
.execute(&pool)
|
||||
.await;
|
||||
}
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Set ModLogs")
|
||||
.description(format!(
|
||||
"Evenements actifs:\n{}\n\nUsage: +setmodlogs <event> <on/off>",
|
||||
events.iter().cloned().collect::<Vec<_>>().join(", ")
|
||||
))
|
||||
.color(theme_color(ctx).await),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct SetModlogsCommand;
|
||||
pub static COMMAND_DESCRIPTOR: SetModlogsCommand = SetModlogsCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for SetModlogsCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "set_modlogs",
|
||||
category: "config",
|
||||
params: "[event on/off]",
|
||||
description: "Affiche ou modifie les evenements qui apparaissent dans les logs de moderation.",
|
||||
examples: &["+setmodlogs", "+setmodlogs warn off"],
|
||||
default_aliases: &["smodlog"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 6,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,8 @@ fn owned_component_id(action: &str, owner_id: UserId) -> String {
|
||||
format!("{}:{}", action, owner_id.get())
|
||||
}
|
||||
|
||||
async fn handle_end_giveaway(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let message_id_raw = args
|
||||
.get(1)
|
||||
.or_else(|| args.first())
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
pub async fn handle_endgiveaway(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let message_id_raw = args.first().copied().unwrap_or_default();
|
||||
|
||||
let Ok(message_id) = message_id_raw.trim().parse::<u64>() else {
|
||||
send_embed(
|
||||
@@ -76,21 +72,12 @@ pub async fn handle_end(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if args
|
||||
.first()
|
||||
.map(|v| v.eq_ignore_ascii_case("giveaway"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
handle_end_giveaway(ctx, msg, args).await;
|
||||
return;
|
||||
}
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("End")
|
||||
.description("Usage: +endgiveaway <id_message>")
|
||||
.description("Usage: +end")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
@@ -104,10 +91,10 @@ impl crate::commands::command_contract::CommandSpec for EndCommand {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "end",
|
||||
category: "event",
|
||||
params: "giveaway <id_message>",
|
||||
description: "Permet de stopper instantanement un giveaway avec l'identifiant du message.",
|
||||
examples: &["+endgiveaway 123456789012345678"],
|
||||
default_aliases: &["gend"],
|
||||
params: "aucun",
|
||||
description: "Affiche le panneau interactif pour terminer un giveaway.",
|
||||
examples: &["+end", "+help end"],
|
||||
default_aliases: &["endmenu"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 6,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_endgiveaway_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::end::handle_endgiveaway(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct EndgiveawayCommand;
|
||||
pub static COMMAND_DESCRIPTOR: EndgiveawayCommand = EndgiveawayCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for EndgiveawayCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "endgiveaway",
|
||||
category: "event",
|
||||
params: "<id_message>",
|
||||
description: "Termine instantanement un giveaway a partir de l'identifiant du message.",
|
||||
examples: &["+endgiveaway 123456789012345678", "+help endgiveaway"],
|
||||
default_aliases: &["gend"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 6,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,6 +127,10 @@ async fn show_menu(ctx: &Context, msg: &Message) {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_suggestionsettings(ctx: &Context, msg: &Message, _args: &[&str]) {
|
||||
show_menu(ctx, msg).await;
|
||||
}
|
||||
|
||||
async fn submit_suggestion(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
@@ -199,22 +203,13 @@ async fn submit_suggestion(
|
||||
}
|
||||
|
||||
pub async fn handle_suggestion(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if args
|
||||
.first()
|
||||
.map(|value| value.eq_ignore_ascii_case("settings"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
show_menu(ctx, msg).await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args.is_empty() {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Suggestion")
|
||||
.description("Utilisation: +suggestion <contenu> ou +suggestion settings")
|
||||
.description("Utilisation: +suggestion <contenu>")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
@@ -505,13 +500,9 @@ impl crate::commands::command_contract::CommandSpec for SuggestionCommand {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "suggestion",
|
||||
category: "fun",
|
||||
params: "<contenu...> | settings",
|
||||
description: "Publie une suggestion utilisateur ou ouvre le panneau de configuration.",
|
||||
examples: &[
|
||||
"+suggestion Ameliorer le salon",
|
||||
"+suggestion settings",
|
||||
"+help suggestion",
|
||||
],
|
||||
params: "<contenu...>",
|
||||
description: "Publie une suggestion utilisateur dans le salon configure.",
|
||||
examples: &["+suggestion Ameliorer le salon", "+help suggestion"],
|
||||
default_aliases: &[],
|
||||
allow_in_dm: false,
|
||||
default_permission: 0,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_suggestionsettings_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::suggestion::handle_suggestionsettings(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct SuggestionsettingsCommand;
|
||||
pub static COMMAND_DESCRIPTOR: SuggestionsettingsCommand = SuggestionsettingsCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for SuggestionsettingsCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "suggestionsettings",
|
||||
category: "fun",
|
||||
params: "aucun",
|
||||
description: "Ouvre le panneau de configuration des suggestions du serveur.",
|
||||
examples: &["+suggestionsettings", "+sgset", "+help suggestionsettings"],
|
||||
default_aliases: &["sgset"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
use serenity::builder::{CreateEmbed, CreateMessage};
|
||||
use serenity::http::GuildPagination;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{add_list_fields, send_embed, theme_color};
|
||||
|
||||
pub async fn handle_server(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if args
|
||||
.first()
|
||||
.map(|value| value.eq_ignore_ascii_case("list"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
handle_server_list(ctx, msg).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(guild) = guild_id.to_partial_guild(&ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
if args.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+server pic`, `+server banner` ou `+serverlist`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
match args[0].to_lowercase().as_str() {
|
||||
"pic" | "icon" | "avatar" => {
|
||||
let icon_url = guild.icon_url().unwrap_or_default();
|
||||
|
||||
if icon_url.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Ce serveur n'a pas d'icône.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title(format!("Icône du serveur {}", guild.name))
|
||||
.image(icon_url)
|
||||
.color(0x5865F2);
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
"banner" => {
|
||||
let banner_url = guild.banner_url().unwrap_or_default();
|
||||
|
||||
if banner_url.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Ce serveur n'a pas de bannière.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title(format!("Bannière du serveur {}", guild.name))
|
||||
.image(banner_url)
|
||||
.color(0x5865F2);
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
_ => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+server pic`, `+server banner` ou `+serverlist`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_server_list(ctx: &Context, msg: &Message) {
|
||||
let guilds = guilds_sorted(ctx).await;
|
||||
let lines = guilds
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, (guild_id, name))| format!("{} · {} · `{}`", index + 1, name, guild_id.get()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title("Serveurs du bot")
|
||||
.color(theme_color(ctx).await);
|
||||
embed = add_list_fields(embed, &lines, "Guildes");
|
||||
let _ = msg
|
||||
.channel_id
|
||||
.send_message(&ctx.http, CreateMessage::new().embed(embed))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub(crate) async fn resolve_guild_target(ctx: &Context, input: &str) -> Option<GuildId> {
|
||||
let guilds = guilds_sorted(ctx).await;
|
||||
if let Ok(index) = input.parse::<usize>() {
|
||||
if index >= 1 && index <= guilds.len() {
|
||||
return Some(guilds[index - 1].0);
|
||||
}
|
||||
}
|
||||
|
||||
input
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.map(GuildId::new)
|
||||
.filter(|id| guilds.iter().any(|(guild_id, _)| guild_id == id))
|
||||
}
|
||||
|
||||
async fn guilds_sorted(ctx: &Context) -> Vec<(GuildId, String)> {
|
||||
let mut all_guilds = Vec::new();
|
||||
let mut after: Option<GuildId> = None;
|
||||
|
||||
loop {
|
||||
let page = if let Some(after_id) = after {
|
||||
ctx.http
|
||||
.get_guilds(Some(GuildPagination::After(after_id)), Some(100))
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
ctx.http
|
||||
.get_guilds(None, Some(100))
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
if page.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
after = page.last().map(|guild| guild.id);
|
||||
all_guilds.extend(page.into_iter().map(|guild| (guild.id, guild.name)));
|
||||
|
||||
if all_guilds.len() % 100 != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
all_guilds.sort_by(|a, b| {
|
||||
a.1.to_lowercase()
|
||||
.cmp(&b.1.to_lowercase())
|
||||
.then_with(|| a.0.get().cmp(&b.0.get()))
|
||||
});
|
||||
all_guilds
|
||||
}
|
||||
|
||||
pub struct ServerCommand;
|
||||
pub static COMMAND_DESCRIPTOR: ServerCommand = ServerCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for ServerCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "server",
|
||||
category: "info",
|
||||
params: "pic | banner | list",
|
||||
description: "Affiche licone ou la banniere du serveur, ou liste les serveurs du bot selon la sous commande.",
|
||||
examples: &["+server", "+sr", "+help server"],
|
||||
default_aliases: &["srv"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
|
||||
pub async fn handle_serverbanner(ctx: &Context, msg: &Message, _args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(guild) = guild_id.to_partial_guild(&ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let banner_url = guild.banner_url().unwrap_or_default();
|
||||
if banner_url.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Ce serveur n'a pas de banniere.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title(format!("Banniere du serveur {}", guild.name))
|
||||
.image(banner_url)
|
||||
.color(0x5865F2);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub struct ServerbannerCommand;
|
||||
pub static COMMAND_DESCRIPTOR: ServerbannerCommand = ServerbannerCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for ServerbannerCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "serverbanner",
|
||||
category: "info",
|
||||
params: "aucun",
|
||||
description: "Affiche la banniere du serveur courant.",
|
||||
examples: &["+serverbanner", "+sbn", "+help serverbanner"],
|
||||
default_aliases: &["sbn"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use serenity::builder::{CreateEmbed, CreateMessage};
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{add_list_fields, theme_color};
|
||||
|
||||
pub async fn handle_serverlist(ctx: &Context, msg: &Message, _args: &[&str]) {
|
||||
let guilds = crate::commands::servertarget::guilds_sorted(ctx).await;
|
||||
let lines = guilds
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, (guild_id, name))| format!("{} · {} · `{}`", index + 1, name, guild_id.get()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title("Serveurs du bot")
|
||||
.color(theme_color(ctx).await);
|
||||
embed = add_list_fields(embed, &lines, "Guildes");
|
||||
|
||||
let _ = msg
|
||||
.channel_id
|
||||
.send_message(&ctx.http, CreateMessage::new().embed(embed))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct ServerlistCommand;
|
||||
pub static COMMAND_DESCRIPTOR: ServerlistCommand = ServerlistCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for ServerlistCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "serverlist",
|
||||
category: "info",
|
||||
params: "aucun",
|
||||
description: "Liste les serveurs ou le bot est present.",
|
||||
examples: &["+serverlist", "+sls", "+help serverlist"],
|
||||
default_aliases: &["sls"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
|
||||
pub async fn handle_serverpic(ctx: &Context, msg: &Message, _args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(guild) = guild_id.to_partial_guild(&ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let icon_url = guild.icon_url().unwrap_or_default();
|
||||
if icon_url.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Ce serveur n'a pas d'icone.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title(format!("Icone du serveur {}", guild.name))
|
||||
.image(icon_url)
|
||||
.color(0x5865F2);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub struct ServerpicCommand;
|
||||
pub static COMMAND_DESCRIPTOR: ServerpicCommand = ServerpicCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for ServerpicCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "serverpic",
|
||||
category: "info",
|
||||
params: "aucun",
|
||||
description: "Affiche l'icone du serveur courant.",
|
||||
examples: &["+serverpic", "+spi", "+help serverpic"],
|
||||
default_aliases: &["spi"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use serenity::http::GuildPagination;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub(crate) async fn resolve_guild_target(ctx: &Context, input: &str) -> Option<GuildId> {
|
||||
let guilds = guilds_sorted(ctx).await;
|
||||
if let Ok(index) = input.parse::<usize>() {
|
||||
if index >= 1 && index <= guilds.len() {
|
||||
return Some(guilds[index - 1].0);
|
||||
}
|
||||
}
|
||||
|
||||
input
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.map(GuildId::new)
|
||||
.filter(|id| guilds.iter().any(|(guild_id, _)| guild_id == id))
|
||||
}
|
||||
|
||||
pub(crate) async fn guilds_sorted(ctx: &Context) -> Vec<(GuildId, String)> {
|
||||
let mut all_guilds = Vec::new();
|
||||
let mut after: Option<GuildId> = None;
|
||||
|
||||
loop {
|
||||
let page = if let Some(after_id) = after {
|
||||
ctx.http
|
||||
.get_guilds(Some(GuildPagination::After(after_id)), Some(100))
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
ctx.http
|
||||
.get_guilds(None, Some(100))
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
if page.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
after = page.last().map(|guild| guild.id);
|
||||
all_guilds.extend(page.into_iter().map(|guild| (guild.id, guild.name)));
|
||||
|
||||
if all_guilds.len() % 100 != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
all_guilds.sort_by(|a, b| {
|
||||
a.1.to_lowercase()
|
||||
.cmp(&b.1.to_lowercase())
|
||||
.then_with(|| a.0.get().cmp(&b.0.get()))
|
||||
});
|
||||
all_guilds
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::parse_channel_id;
|
||||
use crate::db::DbPoolKey;
|
||||
|
||||
pub async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
}
|
||||
|
||||
pub fn parse_target_channel(msg: &Message, args: &[&str], idx: usize) -> Option<ChannelId> {
|
||||
args.get(idx)
|
||||
.and_then(|raw| parse_channel_id(raw))
|
||||
.or(Some(msg.channel_id))
|
||||
}
|
||||
|
||||
pub async fn set_log_channel(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
log_type: &str,
|
||||
channel_id: Option<ChannelId>,
|
||||
enabled: bool,
|
||||
) {
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
|
||||
let _ = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO bot_log_channels (bot_id, guild_id, log_type, channel_id, enabled)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
ON CONFLICT (bot_id, guild_id, log_type)
|
||||
DO UPDATE SET channel_id = EXCLUDED.channel_id, enabled = EXCLUDED.enabled, updated_at = NOW();
|
||||
"#,
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.bind(log_type)
|
||||
.bind(channel_id.map(|c| c.get() as i64))
|
||||
.bind(enabled)
|
||||
.execute(&pool)
|
||||
.await;
|
||||
}
|
||||
@@ -1,661 +0,0 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use chrono::Utc;
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::db::DbPoolKey;
|
||||
|
||||
async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
}
|
||||
|
||||
async fn get_log_channel(ctx: &Context, guild_id: GuildId, log_type: &str) -> Option<ChannelId> {
|
||||
let pool = pool(ctx).await?;
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
|
||||
let row = sqlx::query_as::<_, (Option<i64>, bool)>(
|
||||
r#"
|
||||
SELECT channel_id, enabled
|
||||
FROM bot_log_channels
|
||||
WHERE bot_id = $1 AND guild_id = $2 AND log_type = $3
|
||||
LIMIT 1;
|
||||
"#,
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.bind(log_type)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()?;
|
||||
|
||||
if !row.1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
row.0
|
||||
.and_then(|id| u64::try_from(id).ok().map(ChannelId::new))
|
||||
}
|
||||
|
||||
async fn is_nolog_channel(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
channel_id: ChannelId,
|
||||
kind: &str,
|
||||
) -> bool {
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return false;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
|
||||
let row = sqlx::query_as::<_, (bool, bool)>(
|
||||
r#"
|
||||
SELECT disable_message, disable_voice
|
||||
FROM bot_nolog_channels
|
||||
WHERE bot_id = $1 AND guild_id = $2 AND channel_id = $3
|
||||
LIMIT 1;
|
||||
"#,
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.bind(channel_id.get() as i64)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let Some((disable_message, disable_voice)) = row else {
|
||||
return false;
|
||||
};
|
||||
|
||||
match kind {
|
||||
"message" => disable_message,
|
||||
"voice" => disable_voice,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
async fn record_audit_log(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
log_type: &str,
|
||||
user_id: Option<UserId>,
|
||||
channel_id: Option<ChannelId>,
|
||||
role_id: Option<RoleId>,
|
||||
action: &str,
|
||||
) {
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let _ = crate::db::insert_audit_log(
|
||||
&pool, bot_id, guild_id, log_type, user_id, channel_id, role_id, None, action, None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn send_log_embed(ctx: &Context, guild_id: GuildId, log_type: &str, embed: CreateEmbed) {
|
||||
record_audit_log(ctx, guild_id, log_type, None, None, None, log_type).await;
|
||||
|
||||
if let Some(channel_id) = get_log_channel(ctx, guild_id, log_type).await {
|
||||
let _ = channel_id
|
||||
.send_message(
|
||||
&ctx.http,
|
||||
serenity::builder::CreateMessage::new().embed(embed),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn emit_log(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
log_type: &str,
|
||||
user_id: Option<UserId>,
|
||||
channel_id: Option<ChannelId>,
|
||||
role_id: Option<RoleId>,
|
||||
action: &str,
|
||||
mut embed: CreateEmbed,
|
||||
) {
|
||||
let timestamp = Utc::now();
|
||||
|
||||
embed = embed.timestamp(timestamp);
|
||||
|
||||
record_audit_log(
|
||||
ctx, guild_id, log_type, user_id, channel_id, role_id, action,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(log_channel_id) = get_log_channel(ctx, guild_id, log_type).await {
|
||||
let _ = log_channel_id
|
||||
.send_message(
|
||||
&ctx.http,
|
||||
serenity::builder::CreateMessage::new().embed(embed),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn on_member_join(ctx: &Context, guild_id: GuildId, user: &User) {
|
||||
emit_log(
|
||||
ctx,
|
||||
guild_id,
|
||||
"raid",
|
||||
Some(user.id),
|
||||
None,
|
||||
None,
|
||||
"join",
|
||||
CreateEmbed::new().title("RaidLog").description(format!(
|
||||
"Nouveau membre: <@{}> (`{}`)",
|
||||
user.id.get(),
|
||||
user.tag()
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
run_join_leave_action(ctx, guild_id, "join", user).await;
|
||||
}
|
||||
|
||||
pub async fn on_member_leave(ctx: &Context, guild_id: GuildId, user: &User) {
|
||||
run_join_leave_action(ctx, guild_id, "leave", user).await;
|
||||
}
|
||||
|
||||
async fn run_join_leave_action(ctx: &Context, guild_id: GuildId, kind: &str, user: &User) {
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
|
||||
let row = sqlx::query_as::<_, (bool, Option<i64>, Option<String>)>(
|
||||
r#"
|
||||
SELECT enabled, channel_id, custom_message
|
||||
FROM bot_join_leave_settings
|
||||
WHERE bot_id = $1 AND guild_id = $2 AND kind = $3
|
||||
LIMIT 1;
|
||||
"#,
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.bind(kind)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let Some((enabled, channel_id, custom_message)) = row else {
|
||||
return;
|
||||
};
|
||||
if !enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let channel_id = channel_id
|
||||
.and_then(|id| u64::try_from(id).ok().map(ChannelId::new))
|
||||
.unwrap_or_else(|| ChannelId::new(guild_id.get()));
|
||||
|
||||
let content = custom_message.unwrap_or_else(|| {
|
||||
if kind == "join" {
|
||||
format!("Bienvenue <@{}> !", user.id.get())
|
||||
} else {
|
||||
format!("<@{}> a quitté le serveur.", user.id.get())
|
||||
}
|
||||
});
|
||||
|
||||
let _ = channel_id.say(&ctx.http, content).await;
|
||||
}
|
||||
|
||||
pub async fn send_boost_embed(ctx: &Context, guild_id: GuildId, user: &User) {
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
|
||||
let row = sqlx::query_as::<_, (bool, Option<String>, Option<String>, Option<i32>)>(
|
||||
r#"
|
||||
SELECT enabled, title, description, color
|
||||
FROM bot_boost_embed
|
||||
WHERE bot_id = $1 AND guild_id = $2
|
||||
LIMIT 1;
|
||||
"#,
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let enabled = row.as_ref().map(|r| r.0).unwrap_or(true);
|
||||
if !enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let title = row
|
||||
.as_ref()
|
||||
.and_then(|r| r.1.clone())
|
||||
.unwrap_or_else(|| "Nouveau boost".to_string());
|
||||
let description = row
|
||||
.as_ref()
|
||||
.and_then(|r| r.2.clone())
|
||||
.unwrap_or_else(|| format!("<@{}> vient de booster le serveur !", user.id.get()));
|
||||
let color = row
|
||||
.as_ref()
|
||||
.and_then(|r| r.3)
|
||||
.map(|c| c.max(0) as u32)
|
||||
.unwrap_or(0xF47FFF);
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title(title)
|
||||
.description(description)
|
||||
.color(color);
|
||||
|
||||
if let Some(channel_id) = get_log_channel(ctx, guild_id, "boost").await {
|
||||
let _ = channel_id
|
||||
.send_message(
|
||||
&ctx.http,
|
||||
serenity::builder::CreateMessage::new().embed(embed),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn on_message_deleted(
|
||||
ctx: &Context,
|
||||
guild_id: Option<GuildId>,
|
||||
channel_id: ChannelId,
|
||||
message_id: MessageId,
|
||||
author_id: Option<UserId>,
|
||||
content: Option<String>,
|
||||
) {
|
||||
let Some(guild_id) = guild_id else {
|
||||
return;
|
||||
};
|
||||
if is_nolog_channel(ctx, guild_id, channel_id, "message").await {
|
||||
return;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Message supprimé")
|
||||
.description(format!(
|
||||
"Salon: <#{}>\nAuteur: {}\nMessage: `{}`\nContenu: {}",
|
||||
channel_id.get(),
|
||||
author_id
|
||||
.map(|id| format!("<@{}>", id.get()))
|
||||
.unwrap_or_else(|| "inconnu".to_string()),
|
||||
message_id.get(),
|
||||
content.unwrap_or_else(|| "(indisponible)".to_string())
|
||||
));
|
||||
send_log_embed(ctx, guild_id, "message", embed).await;
|
||||
}
|
||||
|
||||
pub async fn on_message_edited(
|
||||
ctx: &Context,
|
||||
guild_id: Option<GuildId>,
|
||||
channel_id: ChannelId,
|
||||
author_id: Option<UserId>,
|
||||
before: Option<String>,
|
||||
after: Option<String>,
|
||||
) {
|
||||
let Some(guild_id) = guild_id else {
|
||||
return;
|
||||
};
|
||||
if is_nolog_channel(ctx, guild_id, channel_id, "message").await {
|
||||
return;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Message édité")
|
||||
.description(format!(
|
||||
"Salon: <#{}>\nAuteur: {}\nAvant: {}\nAprès: {}",
|
||||
channel_id.get(),
|
||||
author_id
|
||||
.map(|id| format!("<@{}>", id.get()))
|
||||
.unwrap_or_else(|| "inconnu".to_string()),
|
||||
before.unwrap_or_else(|| "(indisponible)".to_string()),
|
||||
after.unwrap_or_else(|| "(indisponible)".to_string())
|
||||
));
|
||||
|
||||
send_log_embed(ctx, guild_id, "message", embed).await;
|
||||
}
|
||||
|
||||
pub async fn on_voice_update(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
user_id: UserId,
|
||||
old_channel: Option<ChannelId>,
|
||||
new_channel: Option<ChannelId>,
|
||||
) {
|
||||
if let Some(ch) = new_channel.or(old_channel) {
|
||||
if is_nolog_channel(ctx, guild_id, ch, "voice").await {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let action = match (old_channel, new_channel) {
|
||||
(None, Some(to)) => format!("<@{}> a rejoint <#{}>", user_id.get(), to.get()),
|
||||
(Some(from), None) => format!("<@{}> a quitté <#{}>", user_id.get(), from.get()),
|
||||
(Some(from), Some(to)) => format!(
|
||||
"<@{}> a bougé de <#{}> vers <#{}>",
|
||||
user_id.get(),
|
||||
from.get(),
|
||||
to.get()
|
||||
),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
send_log_embed(
|
||||
ctx,
|
||||
guild_id,
|
||||
"voice",
|
||||
CreateEmbed::new().title("VoiceLog").description(action),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn on_role_create(ctx: &Context, guild_id: GuildId, role: &Role) {
|
||||
send_log_embed(
|
||||
ctx,
|
||||
guild_id,
|
||||
"role",
|
||||
CreateEmbed::new().title("Role créé").description(format!(
|
||||
"<@&{}> (`{}`)",
|
||||
role.id.get(),
|
||||
role.name
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn on_role_update(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
old_role: Option<&Role>,
|
||||
new_role: &Role,
|
||||
) {
|
||||
let desc = if let Some(old) = old_role {
|
||||
format!(
|
||||
"`{}` -> `{}`\nID: <@&{}>",
|
||||
old.name,
|
||||
new_role.name,
|
||||
new_role.id.get()
|
||||
)
|
||||
} else {
|
||||
format!("Role mis à jour: <@&{}>", new_role.id.get())
|
||||
};
|
||||
|
||||
send_log_embed(
|
||||
ctx,
|
||||
guild_id,
|
||||
"role",
|
||||
CreateEmbed::new().title("Role modifié").description(desc),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn on_role_delete(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
role_id: RoleId,
|
||||
role: Option<&Role>,
|
||||
) {
|
||||
let desc = role
|
||||
.map(|r| format!("`{}` (`{}`)", r.name, role_id.get()))
|
||||
.unwrap_or_else(|| format!("ID `{}`", role_id.get()));
|
||||
send_log_embed(
|
||||
ctx,
|
||||
guild_id,
|
||||
"role",
|
||||
CreateEmbed::new().title("Role supprimé").description(desc),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn on_member_roles_updated(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
user_id: UserId,
|
||||
old_roles: &[RoleId],
|
||||
new_roles: &[RoleId],
|
||||
) {
|
||||
let old_set = old_roles.iter().copied().collect::<BTreeSet<_>>();
|
||||
let new_set = new_roles.iter().copied().collect::<BTreeSet<_>>();
|
||||
|
||||
let added = new_set
|
||||
.difference(&old_set)
|
||||
.map(|r| format!("<@&{}>", r.get()))
|
||||
.collect::<Vec<_>>();
|
||||
let removed = old_set
|
||||
.difference(&new_set)
|
||||
.map(|r| format!("<@&{}>", r.get()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if added.is_empty() && removed.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let desc = format!(
|
||||
"Membre: <@{}>\nAjoutés: {}\nRetirés: {}",
|
||||
user_id.get(),
|
||||
if added.is_empty() {
|
||||
"aucun".to_string()
|
||||
} else {
|
||||
added.join(", ")
|
||||
},
|
||||
if removed.is_empty() {
|
||||
"aucun".to_string()
|
||||
} else {
|
||||
removed.join(", ")
|
||||
}
|
||||
);
|
||||
|
||||
send_log_embed(
|
||||
ctx,
|
||||
guild_id,
|
||||
"role",
|
||||
CreateEmbed::new().title("RoleLog membre").description(desc),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn on_boost_update(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
user_id: UserId,
|
||||
old_boost: Option<Timestamp>,
|
||||
new_boost: Option<Timestamp>,
|
||||
) {
|
||||
match (old_boost, new_boost) {
|
||||
(None, Some(_)) => {
|
||||
send_log_embed(
|
||||
ctx,
|
||||
guild_id,
|
||||
"boost",
|
||||
CreateEmbed::new()
|
||||
.title("Nouveau boost")
|
||||
.description(format!("<@{}> a boost le serveur.", user_id.get())),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Ok(user) = ctx.http.get_user(user_id).await {
|
||||
send_boost_embed(ctx, guild_id, &user).await;
|
||||
}
|
||||
}
|
||||
(Some(_), None) => {
|
||||
send_log_embed(
|
||||
ctx,
|
||||
guild_id,
|
||||
"boost",
|
||||
CreateEmbed::new()
|
||||
.title("Boost retiré")
|
||||
.description(format!("<@{}> ne boost plus le serveur.", user_id.get())),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn log_moderation_command(ctx: &Context, msg: &Message, command: &str, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let enabled = is_modlog_event_enabled(ctx, guild_id, command).await;
|
||||
if !enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let content = if args.is_empty() {
|
||||
command.to_string()
|
||||
} else {
|
||||
format!("{} {}", command, args.join(" "))
|
||||
};
|
||||
|
||||
emit_log(
|
||||
ctx,
|
||||
guild_id,
|
||||
"moderation",
|
||||
Some(msg.author.id),
|
||||
Some(msg.channel_id),
|
||||
None,
|
||||
command,
|
||||
CreateEmbed::new().title("ModLog").description(format!(
|
||||
"Modérateur: <@{}>\nCommande: `+{}`",
|
||||
msg.author.id.get(),
|
||||
content
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn is_modlog_event_enabled(ctx: &Context, guild_id: GuildId, event: &str) -> bool {
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return true;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
|
||||
let row = sqlx::query_as::<_, (String,)>(
|
||||
r#"
|
||||
SELECT modlog_events
|
||||
FROM bot_log_settings
|
||||
WHERE bot_id = $1 AND guild_id = $2
|
||||
LIMIT 1;
|
||||
"#,
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let Some((events,)) = row else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let set = events
|
||||
.split(',')
|
||||
.map(|v| v.trim().to_lowercase())
|
||||
.filter(|v| !v.is_empty())
|
||||
.collect::<BTreeSet<_>>();
|
||||
|
||||
set.contains(&event.to_lowercase())
|
||||
}
|
||||
|
||||
pub async fn on_channel_create(ctx: &Context, channel: &GuildChannel) {
|
||||
emit_log(
|
||||
ctx,
|
||||
channel.guild_id,
|
||||
"channel",
|
||||
None,
|
||||
Some(channel.id),
|
||||
None,
|
||||
"créé",
|
||||
CreateEmbed::new()
|
||||
.title("Channel Créé")
|
||||
.description(format!(
|
||||
"Salon: <#{}> \nNom: {} \nType: {}",
|
||||
channel.id.get(),
|
||||
channel.name,
|
||||
match channel.kind {
|
||||
ChannelType::Text => "Texte",
|
||||
ChannelType::Voice => "Vocal",
|
||||
ChannelType::Category => "Catégorie",
|
||||
_ => "Autre",
|
||||
}
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn on_channel_delete(ctx: &Context, channel: &GuildChannel) {
|
||||
emit_log(
|
||||
ctx,
|
||||
channel.guild_id,
|
||||
"channel",
|
||||
None,
|
||||
Some(channel.id),
|
||||
None,
|
||||
"supprimé",
|
||||
CreateEmbed::new()
|
||||
.title("Channel Supprimé")
|
||||
.description(format!(
|
||||
"Salon: {}\nNom: {}\nType: {}",
|
||||
channel.id.get(),
|
||||
channel.name,
|
||||
match channel.kind {
|
||||
ChannelType::Text => "Texte",
|
||||
ChannelType::Voice => "Vocal",
|
||||
ChannelType::Category => "Catégorie",
|
||||
_ => "Autre",
|
||||
}
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn on_channel_update(ctx: &Context, old_data: Option<GuildChannel>, new: &GuildChannel) {
|
||||
let mut changes = Vec::new();
|
||||
|
||||
if let Some(old) = old_data {
|
||||
if old.name != new.name {
|
||||
changes.push(format!("Nom: `{}` → `{}`", old.name, new.name));
|
||||
}
|
||||
if old.topic != new.topic {
|
||||
changes.push(format!(
|
||||
"Sujet: `{}` → `{}`",
|
||||
old.topic.as_deref().unwrap_or("(vide)"),
|
||||
new.topic.as_deref().unwrap_or("(vide)")
|
||||
));
|
||||
}
|
||||
if old.nsfw != new.nsfw {
|
||||
changes.push(format!("NSFW: {} → {}", old.nsfw, new.nsfw));
|
||||
}
|
||||
}
|
||||
|
||||
if changes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
emit_log(
|
||||
ctx,
|
||||
new.guild_id,
|
||||
"channel",
|
||||
None,
|
||||
Some(new.id),
|
||||
None,
|
||||
"modifié",
|
||||
CreateEmbed::new()
|
||||
.title("Channel Modifié")
|
||||
.description(format!(
|
||||
"Salon: <#{}>\n{}",
|
||||
new.id.get(),
|
||||
changes.join("\n")
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
+77
-5
@@ -7,6 +7,8 @@ pub mod admin_service;
|
||||
pub mod advanced_tools;
|
||||
#[path = "perms/alias.rs"]
|
||||
pub mod alias;
|
||||
#[path = "perms/unalias.rs"]
|
||||
pub mod unalias;
|
||||
#[path = "info/alladmins.rs"]
|
||||
pub mod alladmins;
|
||||
#[path = "info/allbots.rs"]
|
||||
@@ -30,6 +32,10 @@ pub mod autoconfiglog;
|
||||
pub mod automod_service;
|
||||
#[path = "automation/autopublish.rs"]
|
||||
pub mod autopublish;
|
||||
#[path = "automation/autopublishoff.rs"]
|
||||
pub mod autopublishoff;
|
||||
#[path = "automation/autopublishon.rs"]
|
||||
pub mod autopublishon;
|
||||
#[path = "automation/autoreact.rs"]
|
||||
pub mod autoreact;
|
||||
#[path = "automation/backup.rs"]
|
||||
@@ -64,6 +70,8 @@ pub mod button;
|
||||
pub mod calc;
|
||||
#[path = "botconfig/change.rs"]
|
||||
pub mod change;
|
||||
#[path = "botconfig/changereset.rs"]
|
||||
pub mod changereset;
|
||||
#[path = "botconfig/changeall.rs"]
|
||||
pub mod changeall;
|
||||
#[path = "info/channel.rs"]
|
||||
@@ -102,6 +110,8 @@ pub mod compet;
|
||||
pub mod create;
|
||||
#[path = "perms/del.rs"]
|
||||
pub mod del;
|
||||
#[path = "perms/delperm.rs"]
|
||||
pub mod delperm;
|
||||
#[path = "mod/delsanction.rs"]
|
||||
pub mod del_sanction;
|
||||
#[path = "roles/delrole.rs"]
|
||||
@@ -118,6 +128,8 @@ pub mod embed;
|
||||
pub mod emoji;
|
||||
#[path = "event/end.rs"]
|
||||
pub mod end;
|
||||
#[path = "event/endgiveaway.rs"]
|
||||
pub mod endgiveaway;
|
||||
#[path = "event/giveaway.rs"]
|
||||
pub mod giveaway;
|
||||
#[path = "perms/help.rs"]
|
||||
@@ -169,6 +181,12 @@ pub mod moderation_tools;
|
||||
pub mod modlog;
|
||||
#[path = "owner/mp.rs"]
|
||||
pub mod mp;
|
||||
#[path = "owner/mpdelete.rs"]
|
||||
pub mod mpdelete;
|
||||
#[path = "owner/mpsent.rs"]
|
||||
pub mod mpsent;
|
||||
#[path = "owner/mpsettings.rs"]
|
||||
pub mod mpsettings;
|
||||
#[path = "mod/mute.rs"]
|
||||
pub mod mute;
|
||||
#[path = "mod/mutelist.rs"]
|
||||
@@ -179,6 +197,10 @@ pub mod muterole;
|
||||
pub mod newsticker;
|
||||
#[path = "roles/noderank.rs"]
|
||||
pub mod noderank;
|
||||
#[path = "roles/noderankadd.rs"]
|
||||
pub mod noderankadd;
|
||||
#[path = "roles/noderankdel.rs"]
|
||||
pub mod noderankdel;
|
||||
#[path = "config/nolog.rs"]
|
||||
pub mod nolog;
|
||||
#[path = "botconfig/online.rs"]
|
||||
@@ -193,6 +215,10 @@ pub mod perms_service;
|
||||
pub mod pic;
|
||||
#[path = "automation/piconly.rs"]
|
||||
pub mod piconly;
|
||||
#[path = "automation/piconlyadd.rs"]
|
||||
pub mod piconlyadd;
|
||||
#[path = "automation/piconlydel.rs"]
|
||||
pub mod piconlydel;
|
||||
#[path = "info/ping.rs"]
|
||||
pub mod ping;
|
||||
#[path = "botconfig/playto.rs"]
|
||||
@@ -203,6 +229,12 @@ pub mod prefix;
|
||||
pub mod public;
|
||||
#[path = "mod/punish.rs"]
|
||||
pub mod punish;
|
||||
#[path = "mod/punishadd.rs"]
|
||||
pub mod punishadd;
|
||||
#[path = "mod/punishdel.rs"]
|
||||
pub mod punishdel;
|
||||
#[path = "mod/punishsetup.rs"]
|
||||
pub mod punishsetup;
|
||||
#[path = "config/raidlog.rs"]
|
||||
pub mod raidlog;
|
||||
#[path = "botconfig/removeactivity.rs"]
|
||||
@@ -227,12 +259,28 @@ pub mod rolemenu;
|
||||
pub mod sanctions;
|
||||
#[path = "fun/say.rs"]
|
||||
pub mod say;
|
||||
#[path = "info/server.rs"]
|
||||
pub mod server;
|
||||
#[path = "info/serverbanner.rs"]
|
||||
pub mod serverbanner;
|
||||
#[path = "info/serverlist.rs"]
|
||||
pub mod serverlist;
|
||||
#[path = "info/serverpic.rs"]
|
||||
pub mod serverpic;
|
||||
#[path = "info/servertarget.rs"]
|
||||
pub mod servertarget;
|
||||
#[path = "info/serverinfo.rs"]
|
||||
pub mod serverinfo;
|
||||
#[path = "botconfig/set.rs"]
|
||||
pub mod set;
|
||||
#[path = "botconfig/setbanner.rs"]
|
||||
pub mod setbanner;
|
||||
#[path = "botconfig/setname.rs"]
|
||||
pub mod setname;
|
||||
#[path = "botconfig/setperm.rs"]
|
||||
pub mod setperm;
|
||||
#[path = "botconfig/setpic.rs"]
|
||||
pub mod setpic;
|
||||
#[path = "botconfig/setprofil.rs"]
|
||||
pub mod setprofil;
|
||||
#[path = "config/setboostembed.rs"]
|
||||
pub mod set_boostembed;
|
||||
#[path = "config/setmodlogs.rs"]
|
||||
@@ -255,6 +303,8 @@ pub mod stream;
|
||||
pub mod strikes;
|
||||
#[path = "fun/suggestion.rs"]
|
||||
pub mod suggestion;
|
||||
#[path = "fun/suggestionsettings.rs"]
|
||||
pub mod suggestionsettings;
|
||||
#[path = "roles/sync.rs"]
|
||||
pub mod sync;
|
||||
#[path = "mod/tempban.rs"]
|
||||
@@ -343,10 +393,13 @@ pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
||||
member::COMMAND_DESCRIPTOR.metadata(),
|
||||
pic::COMMAND_DESCRIPTOR.metadata(),
|
||||
banner::COMMAND_DESCRIPTOR.metadata(),
|
||||
server::COMMAND_DESCRIPTOR.metadata(),
|
||||
serverpic::COMMAND_DESCRIPTOR.metadata(),
|
||||
serverbanner::COMMAND_DESCRIPTOR.metadata(),
|
||||
serverlist::COMMAND_DESCRIPTOR.metadata(),
|
||||
snipe::COMMAND_DESCRIPTOR.metadata(),
|
||||
emoji::COMMAND_DESCRIPTOR.metadata(),
|
||||
giveaway::COMMAND_DESCRIPTOR.metadata(),
|
||||
endgiveaway::COMMAND_DESCRIPTOR.metadata(),
|
||||
modlog::COMMAND_DESCRIPTOR.metadata(),
|
||||
messagelog::COMMAND_DESCRIPTOR.metadata(),
|
||||
voicelog::COMMAND_DESCRIPTOR.metadata(),
|
||||
@@ -376,6 +429,9 @@ pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
||||
link::COMMAND_DESCRIPTOR.metadata(),
|
||||
strikes::COMMAND_DESCRIPTOR.metadata(),
|
||||
punish::COMMAND_DESCRIPTOR.metadata(),
|
||||
punishsetup::COMMAND_DESCRIPTOR.metadata(),
|
||||
punishadd::COMMAND_DESCRIPTOR.metadata(),
|
||||
punishdel::COMMAND_DESCRIPTOR.metadata(),
|
||||
public::COMMAND_DESCRIPTOR.metadata(),
|
||||
resetantiraide::COMMAND_DESCRIPTOR.metadata(),
|
||||
backup::COMMAND_DESCRIPTOR.metadata(),
|
||||
@@ -387,8 +443,13 @@ pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
||||
tickets::COMMAND_DESCRIPTOR.metadata(),
|
||||
showpics::COMMAND_DESCRIPTOR.metadata(),
|
||||
piconly::COMMAND_DESCRIPTOR.metadata(),
|
||||
piconlyadd::COMMAND_DESCRIPTOR.metadata(),
|
||||
piconlydel::COMMAND_DESCRIPTOR.metadata(),
|
||||
suggestion::COMMAND_DESCRIPTOR.metadata(),
|
||||
suggestionsettings::COMMAND_DESCRIPTOR.metadata(),
|
||||
autopublish::COMMAND_DESCRIPTOR.metadata(),
|
||||
autopublishon::COMMAND_DESCRIPTOR.metadata(),
|
||||
autopublishoff::COMMAND_DESCRIPTOR.metadata(),
|
||||
tempvoc::COMMAND_DESCRIPTOR.metadata(),
|
||||
tempvoc_cmd::COMMAND_DESCRIPTOR.metadata(),
|
||||
autobackup::COMMAND_DESCRIPTOR.metadata(),
|
||||
@@ -430,6 +491,8 @@ pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
||||
delrole::COMMAND_DESCRIPTOR.metadata(),
|
||||
derank::COMMAND_DESCRIPTOR.metadata(),
|
||||
noderank::COMMAND_DESCRIPTOR.metadata(),
|
||||
noderankadd::COMMAND_DESCRIPTOR.metadata(),
|
||||
noderankdel::COMMAND_DESCRIPTOR.metadata(),
|
||||
del_sanction::COMMAND_DESCRIPTOR.metadata(),
|
||||
clear_sanctions::COMMAND_DESCRIPTOR.metadata(),
|
||||
clear_all_sanctions::COMMAND_DESCRIPTOR.metadata(),
|
||||
@@ -440,7 +503,11 @@ pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
||||
autoreact::COMMAND_DESCRIPTOR.metadata(),
|
||||
calc::COMMAND_DESCRIPTOR.metadata(),
|
||||
shadowbot::COMMAND_DESCRIPTOR.metadata(),
|
||||
set::COMMAND_DESCRIPTOR.metadata(),
|
||||
setname::COMMAND_DESCRIPTOR.metadata(),
|
||||
setpic::COMMAND_DESCRIPTOR.metadata(),
|
||||
setbanner::COMMAND_DESCRIPTOR.metadata(),
|
||||
setprofil::COMMAND_DESCRIPTOR.metadata(),
|
||||
setperm::COMMAND_DESCRIPTOR.metadata(),
|
||||
theme::COMMAND_DESCRIPTOR.metadata(),
|
||||
playto::COMMAND_DESCRIPTOR.metadata(),
|
||||
listen::COMMAND_DESCRIPTOR.metadata(),
|
||||
@@ -461,17 +528,22 @@ pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
||||
clear_bl::COMMAND_DESCRIPTOR.metadata(),
|
||||
say::COMMAND_DESCRIPTOR.metadata(),
|
||||
change::COMMAND_DESCRIPTOR.metadata(),
|
||||
changereset::COMMAND_DESCRIPTOR.metadata(),
|
||||
changeall::COMMAND_DESCRIPTOR.metadata(),
|
||||
mainprefix::COMMAND_DESCRIPTOR.metadata(),
|
||||
prefix::COMMAND_DESCRIPTOR.metadata(),
|
||||
perms::COMMAND_DESCRIPTOR.metadata(),
|
||||
del::COMMAND_DESCRIPTOR.metadata(),
|
||||
delperm::COMMAND_DESCRIPTOR.metadata(),
|
||||
clear_perms::COMMAND_DESCRIPTOR.metadata(),
|
||||
allperms::COMMAND_DESCRIPTOR.metadata(),
|
||||
help::COMMAND_DESCRIPTOR.metadata(),
|
||||
helpsetting::COMMAND_DESCRIPTOR.metadata(),
|
||||
alias::COMMAND_DESCRIPTOR.metadata(),
|
||||
unalias::COMMAND_DESCRIPTOR.metadata(),
|
||||
mp::COMMAND_DESCRIPTOR.metadata(),
|
||||
mpsettings::COMMAND_DESCRIPTOR.metadata(),
|
||||
mpsent::COMMAND_DESCRIPTOR.metadata(),
|
||||
mpdelete::COMMAND_DESCRIPTOR.metadata(),
|
||||
invite::COMMAND_DESCRIPTOR.metadata(),
|
||||
leave::COMMAND_DESCRIPTOR.metadata(),
|
||||
leave_settings::COMMAND_DESCRIPTOR.metadata(),
|
||||
|
||||
@@ -39,7 +39,7 @@ impl crate::commands::command_contract::CommandSpec for ClearBadwordsCommand {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "clearbadwords",
|
||||
category: "mod",
|
||||
params: "badwords",
|
||||
params: "aucun",
|
||||
description: "Supprime l ensemble des mots interdits enregistres.",
|
||||
examples: &["+clearbadwords", "+help clearbadwords"],
|
||||
default_aliases: &["cbw"],
|
||||
|
||||
@@ -11,7 +11,7 @@ pub async fn handle_clear_limit(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(raw_value) = args.get(1) else {
|
||||
let Some(raw_value) = args.first() else {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
@@ -63,7 +63,7 @@ impl crate::commands::command_contract::CommandSpec for ClearLimitCommand {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "clearlimit",
|
||||
category: "mod",
|
||||
params: "limit <nombre>",
|
||||
params: "<nombre>",
|
||||
description: "Definit la limite max de messages supprimables avec +clear.",
|
||||
examples: &["+clearlimit 100", "+help clearlimit"],
|
||||
default_aliases: &["climit"],
|
||||
|
||||
@@ -10,11 +10,11 @@ pub async fn handle_clear_sanctions(ctx: &Context, msg: &Message, args: &[&str])
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
if args.len() < 2 {
|
||||
if args.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(target) = parse_user_id(args[1]) else {
|
||||
let Some(target) = parse_user_id(args[0]) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -10,14 +10,14 @@ pub async fn handle_del_sanction(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
if args.len() < 3 {
|
||||
if args.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(target) = parse_user_id(args[1]) else {
|
||||
let Some(target) = parse_user_id(args[0]) else {
|
||||
return;
|
||||
};
|
||||
let Ok(index) = args[2].parse::<usize>() else {
|
||||
let Ok(index) = args[1].parse::<usize>() else {
|
||||
return;
|
||||
};
|
||||
if index == 0 {
|
||||
|
||||
+149
-104
@@ -35,124 +35,169 @@ pub async fn handle_punish(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
|
||||
if args.is_empty() {
|
||||
let rules = db::list_punish_rules(&pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let description = if rules.is_empty() {
|
||||
"Aucune regle.".to_string()
|
||||
} else {
|
||||
rules
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, rule)| describe_rule(idx + 1, rule))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
if !args.is_empty() {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description(description)
|
||||
.color(0x5865F2),
|
||||
.description("Usage: +punish")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args[0].eq_ignore_ascii_case("setup") {
|
||||
let _ = db::setup_default_punish_rules(&pool, bot_id, guild_id_raw).await;
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description("Regles par defaut restaurees.")
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
let rules = db::list_punish_rules(&pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if args[0].eq_ignore_ascii_case("add") {
|
||||
if args.len() < 4 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(threshold) = args[1].parse::<i32>() else {
|
||||
return;
|
||||
};
|
||||
let Some(window_seconds) = parse_duration_to_seconds(args[2]) else {
|
||||
return;
|
||||
};
|
||||
let Some(sanction) = parse_sanction(args[3]) else {
|
||||
return;
|
||||
};
|
||||
let sanction_seconds = args.get(4).and_then(|raw| parse_duration_to_seconds(raw));
|
||||
|
||||
let _ = db::upsert_punish_rule(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
threshold.clamp(1, 200),
|
||||
window_seconds,
|
||||
sanction,
|
||||
sanction_seconds,
|
||||
)
|
||||
.await;
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description("Regle ajoutee ou mise a jour.")
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args[0].eq_ignore_ascii_case("del") {
|
||||
let Some(raw_index) = args.get(1) else {
|
||||
return;
|
||||
};
|
||||
let Ok(index) = raw_index.parse::<usize>() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let rules = db::list_punish_rules(&pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if index == 0 || index > rules.len() {
|
||||
return;
|
||||
}
|
||||
|
||||
let rule = &rules[index - 1];
|
||||
let _ = db::delete_punish_rule_by_id(&pool, bot_id, guild_id_raw, rule.id).await;
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description(format!("Regle {} supprimee.", index))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
let description = if rules.is_empty() {
|
||||
"Aucune regle.".to_string()
|
||||
} else {
|
||||
rules
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, rule)| describe_rule(idx + 1, rule))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description("Usage: +punish | +punish add <nombre> <duree> <sanction> [duree] | +punish del <numero> | +punish setup")
|
||||
.color(0xED4245),
|
||||
.description(description)
|
||||
.color(0x5865F2),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_punishsetup(ctx: &Context, msg: &Message, _args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
|
||||
let _ = db::setup_default_punish_rules(&pool, bot_id, guild_id_raw).await;
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description("Regles par defaut restaurees.")
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_punishadd(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
|
||||
if args.len() < 3 {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description("Usage: +punishadd <nombre> <duree> <sanction> [duree]")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(threshold) = args[0].parse::<i32>() else {
|
||||
return;
|
||||
};
|
||||
let Some(window_seconds) = parse_duration_to_seconds(args[1]) else {
|
||||
return;
|
||||
};
|
||||
let Some(sanction) = parse_sanction(args[2]) else {
|
||||
return;
|
||||
};
|
||||
let sanction_seconds = args.get(3).and_then(|raw| parse_duration_to_seconds(raw));
|
||||
|
||||
let _ = db::upsert_punish_rule(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
threshold.clamp(1, 200),
|
||||
window_seconds,
|
||||
sanction,
|
||||
sanction_seconds,
|
||||
)
|
||||
.await;
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description("Regle ajoutee ou mise a jour.")
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_punishdel(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
|
||||
let Some(raw_index) = args.first() else {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description("Usage: +punishdel <numero>")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
let Ok(index) = raw_index.parse::<usize>() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let rules = db::list_punish_rules(&pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if index == 0 || index > rules.len() {
|
||||
return;
|
||||
}
|
||||
|
||||
let rule = &rules[index - 1];
|
||||
let _ = db::delete_punish_rule_by_id(&pool, bot_id, guild_id_raw, rule.id).await;
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description(format!("Regle {} supprimee.", index))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -165,9 +210,9 @@ impl crate::commands::command_contract::CommandSpec for PunishCommand {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "punish",
|
||||
category: "mod",
|
||||
params: "[add <nombre> <duree> <sanction> [duree] | del <numero> | setup]",
|
||||
description: "Affiche et gere les sanctions automatiques appliquees selon les strikes.",
|
||||
examples: &["+punish", "+punish add 8 1h mute 30m", "+punish setup"],
|
||||
params: "aucun",
|
||||
description: "Affiche les sanctions automatiques appliquees selon les strikes.",
|
||||
examples: &["+punish", "+help punish"],
|
||||
default_aliases: &["pn"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 7,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_punishadd_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::punish::handle_punishadd(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct PunishaddCommand;
|
||||
pub static COMMAND_DESCRIPTOR: PunishaddCommand = PunishaddCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for PunishaddCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "punishadd",
|
||||
category: "mod",
|
||||
params: "<nombre> <duree> <sanction> [duree]",
|
||||
description: "Ajoute ou met a jour une regle Punish.",
|
||||
examples: &["+punishadd 8 1h mute 30m", "+help punishadd"],
|
||||
default_aliases: &["pna"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_punishdel_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::punish::handle_punishdel(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct PunishdelCommand;
|
||||
pub static COMMAND_DESCRIPTOR: PunishdelCommand = PunishdelCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for PunishdelCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "punishdel",
|
||||
category: "mod",
|
||||
params: "<numero>",
|
||||
description: "Supprime une regle Punish par son index.",
|
||||
examples: &["+punishdel 2", "+help punishdel"],
|
||||
default_aliases: &["pnd"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_punishsetup_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::punish::handle_punishsetup(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct PunishsetupCommand;
|
||||
pub static COMMAND_DESCRIPTOR: PunishsetupCommand = PunishsetupCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for PunishsetupCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "punishsetup",
|
||||
category: "mod",
|
||||
params: "aucun",
|
||||
description: "Recharge les regles Punish par defaut.",
|
||||
examples: &["+punishsetup", "+help punishsetup"],
|
||||
default_aliases: &["pnsetup"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ pub async fn handle_set_muterole(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(raw_role) = args.get(1) else {
|
||||
let Some(raw_role) = args.first() else {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
@@ -68,7 +68,7 @@ impl crate::commands::command_contract::CommandSpec for SetMuteRoleCommand {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "setmuterole",
|
||||
category: "mod",
|
||||
params: "muterole <@role/ID/nom>",
|
||||
params: "<@role/ID/nom>",
|
||||
description: "Definit le role utilise pour le mute lorsque le mode timeout est desactive.",
|
||||
examples: &["+setmuterole @Muted", "+help setmuterole"],
|
||||
default_aliases: &["smr"],
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn edit_channel_visibility(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
channel_id: ChannelId,
|
||||
lock: Option<bool>,
|
||||
hide: Option<bool>,
|
||||
) -> bool {
|
||||
let Ok(guild) = guild_id.to_partial_guild(&ctx.http).await else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let everyone_role = guild
|
||||
.roles
|
||||
.values()
|
||||
.find(|r| r.name == "@everyone")
|
||||
.map(|r| r.id);
|
||||
let Some(everyone_role) = everyone_role else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Ok(channels) = guild_id.channels(&ctx.http).await else {
|
||||
return false;
|
||||
};
|
||||
let Some(channel) = channels.get(&channel_id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let mut allow = Permissions::empty();
|
||||
let mut deny = Permissions::empty();
|
||||
|
||||
if let Some(locked) = lock {
|
||||
if channel.kind == ChannelType::Text || channel.kind == ChannelType::News {
|
||||
if locked {
|
||||
deny |= Permissions::SEND_MESSAGES;
|
||||
} else {
|
||||
allow |= Permissions::SEND_MESSAGES;
|
||||
}
|
||||
} else if locked {
|
||||
deny |= Permissions::CONNECT | Permissions::SPEAK;
|
||||
} else {
|
||||
allow |= Permissions::CONNECT | Permissions::SPEAK;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(hidden) = hide {
|
||||
if hidden {
|
||||
deny |= Permissions::VIEW_CHANNEL;
|
||||
} else {
|
||||
allow |= Permissions::VIEW_CHANNEL;
|
||||
}
|
||||
}
|
||||
|
||||
channel_id
|
||||
.create_permission(
|
||||
&ctx.http,
|
||||
PermissionOverwrite {
|
||||
allow,
|
||||
deny,
|
||||
kind: PermissionOverwriteType::Role(everyone_role),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::Utc;
|
||||
use serenity::builder::EditMember;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::admin_common::parse_user_id;
|
||||
use crate::db::{self, DbPoolKey};
|
||||
|
||||
pub fn duration_from_input(input: &str) -> Option<Duration> {
|
||||
let raw = input.trim().to_lowercase();
|
||||
if raw.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut number = String::new();
|
||||
let mut suffix = String::new();
|
||||
for ch in raw.chars() {
|
||||
if ch.is_ascii_digit() {
|
||||
if !suffix.is_empty() {
|
||||
return None;
|
||||
}
|
||||
number.push(ch);
|
||||
} else if !ch.is_whitespace() {
|
||||
suffix.push(ch);
|
||||
}
|
||||
}
|
||||
|
||||
let value = number.parse::<u64>().ok()?;
|
||||
let secs = match suffix.as_str() {
|
||||
"s" | "sec" | "secs" | "seconde" | "secondes" => value,
|
||||
"m" | "min" | "mins" | "minute" | "minutes" => value * 60,
|
||||
"h" | "heure" | "heures" => value * 3600,
|
||||
"j" | "d" | "jour" | "jours" => value * 86400,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(Duration::from_secs(secs.max(1)))
|
||||
}
|
||||
|
||||
pub async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
}
|
||||
|
||||
pub async fn add_sanction(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
user_id: UserId,
|
||||
moderator_id: UserId,
|
||||
kind: &str,
|
||||
reason: &str,
|
||||
channel_id: Option<ChannelId>,
|
||||
expires_at: Option<chrono::DateTime<Utc>>,
|
||||
) {
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let _ = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO bot_sanctions
|
||||
(bot_id, guild_id, user_id, moderator_id, kind, reason, channel_id, expires_at, active)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, TRUE);
|
||||
"#,
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.bind(user_id.get() as i64)
|
||||
.bind(moderator_id.get() as i64)
|
||||
.bind(kind)
|
||||
.bind(reason)
|
||||
.bind(channel_id.map(|c| c.get() as i64))
|
||||
.bind(expires_at)
|
||||
.execute(&pool)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn parse_targets(raw: &str) -> Vec<UserId> {
|
||||
let mut out = Vec::new();
|
||||
for token in raw.split(',') {
|
||||
if let Some(uid) = parse_user_id(token.trim()) {
|
||||
out.push(uid);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub async fn handle_timeout(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
users: &[UserId],
|
||||
expires: Option<chrono::DateTime<Utc>>,
|
||||
) -> usize {
|
||||
let settings = if let Some(pool) = pool(ctx).await {
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
db::get_or_create_moderation_settings(&pool, bot_id, guild_id.get() as i64)
|
||||
.await
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mute_role_id = settings
|
||||
.as_ref()
|
||||
.and_then(|s| s.mute_role_id)
|
||||
.and_then(|raw| u64::try_from(raw).ok())
|
||||
.map(RoleId::new);
|
||||
|
||||
let use_timeout = settings
|
||||
.as_ref()
|
||||
.map(|s| s.use_timeout || s.mute_role_id.is_none())
|
||||
.unwrap_or(true);
|
||||
|
||||
let mut done = 0usize;
|
||||
for user_id in users {
|
||||
if let Ok(mut member) = guild_id.member(&ctx.http, *user_id).await {
|
||||
if use_timeout {
|
||||
let mut builder = EditMember::new();
|
||||
if let Some(ts) = expires {
|
||||
if let Ok(discord_ts) = Timestamp::from_unix_timestamp(ts.timestamp()) {
|
||||
builder = builder.disable_communication_until_datetime(discord_ts);
|
||||
}
|
||||
} else {
|
||||
builder = builder.enable_communication();
|
||||
}
|
||||
|
||||
if member.edit(&ctx.http, builder).await.is_ok() {
|
||||
done += 1;
|
||||
}
|
||||
} else {
|
||||
let Some(role_id) = mute_role_id else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let result = if expires.is_some() {
|
||||
member.add_role(&ctx.http, role_id).await
|
||||
} else {
|
||||
member.remove_role(&ctx.http, role_id).await
|
||||
};
|
||||
|
||||
if result.is_ok() {
|
||||
done += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
done
|
||||
}
|
||||
|
||||
pub async fn channel_mute_users(
|
||||
ctx: &Context,
|
||||
channel_id: ChannelId,
|
||||
users: &[UserId],
|
||||
mute: bool,
|
||||
) -> usize {
|
||||
let mut done = 0usize;
|
||||
for user_id in users {
|
||||
let result = if mute {
|
||||
channel_id
|
||||
.create_permission(
|
||||
&ctx.http,
|
||||
PermissionOverwrite {
|
||||
allow: Permissions::empty(),
|
||||
deny: Permissions::SEND_MESSAGES
|
||||
| Permissions::ADD_REACTIONS
|
||||
| Permissions::SPEAK,
|
||||
kind: PermissionOverwriteType::Member(*user_id),
|
||||
},
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
channel_id
|
||||
.delete_permission(&ctx.http, PermissionOverwriteType::Member(*user_id))
|
||||
.await
|
||||
};
|
||||
|
||||
if result.is_ok() {
|
||||
done += 1;
|
||||
}
|
||||
}
|
||||
done
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use chrono::Utc;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::moderation_sanction_helpers::{channel_mute_users, handle_timeout};
|
||||
use crate::db::DbPoolKey;
|
||||
|
||||
static MODERATION_TICK: OnceLock<Mutex<Instant>> = OnceLock::new();
|
||||
|
||||
async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
}
|
||||
|
||||
pub async fn maybe_run_maintenance(ctx: &Context, guild_id: Option<GuildId>) {
|
||||
let Some(guild_id) = guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let now = Instant::now();
|
||||
let lock = MODERATION_TICK.get_or_init(|| Mutex::new(Instant::now() - Duration::from_secs(60)));
|
||||
{
|
||||
let mut last = lock.lock().expect("moderation tick lock poisoned");
|
||||
if now.duration_since(*last) < Duration::from_secs(30) {
|
||||
return;
|
||||
}
|
||||
*last = now;
|
||||
}
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let now_dt = Utc::now();
|
||||
|
||||
let rows = sqlx::query_as::<_, (i64, i64, String, Option<i64>)>(
|
||||
r#"
|
||||
SELECT id, user_id, kind, channel_id
|
||||
FROM bot_sanctions
|
||||
WHERE bot_id = $1 AND guild_id = $2 AND active = TRUE AND expires_at IS NOT NULL AND expires_at <= $3;
|
||||
"#,
|
||||
)
|
||||
.bind(bot_id.get() as i64)
|
||||
.bind(guild_id.get() as i64)
|
||||
.bind(now_dt)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
for (id, uid, kind, channel_id) in &rows {
|
||||
let user_id = UserId::new(*uid as u64);
|
||||
if kind == "tempmute" {
|
||||
let _ = handle_timeout(ctx, guild_id, &[user_id], None).await;
|
||||
} else if kind == "tempcmute" {
|
||||
if let Some(cid) = channel_id {
|
||||
let _ =
|
||||
channel_mute_users(ctx, ChannelId::new(*cid as u64), &[user_id], false).await;
|
||||
}
|
||||
} else if kind == "tempban" {
|
||||
let _ = guild_id.unban(&ctx.http, user_id).await;
|
||||
}
|
||||
|
||||
let _ = sqlx::query("UPDATE bot_sanctions SET active = FALSE WHERE id = $1")
|
||||
.bind(*id)
|
||||
.execute(&pool)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::commands::server::resolve_guild_target;
|
||||
use crate::commands::servertarget::resolve_guild_target;
|
||||
|
||||
pub async fn handle_discussion(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if args.len() < 2 {
|
||||
|
||||
@@ -3,7 +3,7 @@ use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::commands::server::resolve_guild_target;
|
||||
use crate::commands::servertarget::resolve_guild_target;
|
||||
|
||||
pub async fn handle_invite(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if args.is_empty() {
|
||||
|
||||
@@ -3,7 +3,7 @@ use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::commands::server::resolve_guild_target;
|
||||
use crate::commands::servertarget::resolve_guild_target;
|
||||
|
||||
pub async fn handle_leave(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let target = if args.is_empty() {
|
||||
|
||||
+116
-131
@@ -13,152 +13,137 @@ use crate::db::{
|
||||
log_sent_mp_message, mark_sent_mp_deleted, set_mp_enabled,
|
||||
};
|
||||
|
||||
pub async fn handle_mp(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if args
|
||||
.first()
|
||||
.map(|value| value.eq_ignore_ascii_case("settings"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
if args.len() == 1 {
|
||||
let enabled = get_mp_enabled(&pool, bot_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(true);
|
||||
let embed = CreateEmbed::new()
|
||||
.title("MP settings")
|
||||
.description(format!(
|
||||
"Envoi de MP: `{}`\nUtilise `+mpsettings on/off`.",
|
||||
if enabled { "on" } else { "off" }
|
||||
))
|
||||
.color(0x5865F2);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let enabled = match args[1].to_lowercase().as_str() {
|
||||
"on" | "true" | "yes" => true,
|
||||
"off" | "false" | "no" => false,
|
||||
_ => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+mpsettings <on/off>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let _ = set_mp_enabled(&pool, bot_id, enabled).await;
|
||||
pub async fn handle_mpsettings(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("MP settings mis à jour")
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
if args.is_empty() {
|
||||
let enabled = get_mp_enabled(&pool, bot_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(true);
|
||||
let embed = CreateEmbed::new()
|
||||
.title("MP settings")
|
||||
.description(format!(
|
||||
"Envoi de MP: `{}`",
|
||||
"Envoi de MP: `{}`\nUtilise `+mpsettings on/off`.",
|
||||
if enabled { "on" } else { "off" }
|
||||
))
|
||||
.color(0x57F287);
|
||||
.color(0x5865F2);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args
|
||||
.first()
|
||||
.map(|value| value.eq_ignore_ascii_case("sent"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let page = args
|
||||
.get(1)
|
||||
.and_then(|value| value.parse::<i64>().ok())
|
||||
.filter(|value| *value >= 1)
|
||||
.unwrap_or(1);
|
||||
let _ = send_mp_sent_page(ctx, msg, page).await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args
|
||||
.first()
|
||||
.map(|value| value.eq_ignore_ascii_case("delete") || value.eq_ignore_ascii_case("del"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let Some(entry_id_raw) = args.get(1) else {
|
||||
let enabled = match args[0].to_lowercase().as_str() {
|
||||
"on" | "true" | "yes" => true,
|
||||
"off" | "false" | "no" => false,
|
||||
_ => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+mpdelete <id>`")
|
||||
.description("Usage: `+mpsettings <on/off>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(entry_id) = entry_id_raw.parse::<i64>() else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("ID invalide.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(entry) = get_sent_mp_message(&pool, bot_id, entry_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Message MP introuvable.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let delete_result = ChannelId::new(entry.dm_channel_id as u64)
|
||||
.delete_message(&ctx.http, MessageId::new(entry.message_id as u64))
|
||||
.await;
|
||||
|
||||
let _ = mark_sent_mp_deleted(&pool, bot_id, entry_id).await;
|
||||
if delete_result.is_err() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("MP déjà supprimé ou inaccessible")
|
||||
.description(format!(
|
||||
"Entrée `#{}` marquée supprimée en base (Discord a refusé la suppression).",
|
||||
entry.entry_id
|
||||
))
|
||||
.color(0xFEE75C);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
} else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("MP supprimé")
|
||||
.description(format!("Entrée `#{}` supprimée.", entry.entry_id))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let _ = set_mp_enabled(&pool, bot_id, enabled).await;
|
||||
let embed = CreateEmbed::new()
|
||||
.title("MP settings mis à jour")
|
||||
.description(format!(
|
||||
"Envoi de MP: `{}`",
|
||||
if enabled { "on" } else { "off" }
|
||||
))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_mpsent(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let page = args
|
||||
.first()
|
||||
.and_then(|value| value.parse::<i64>().ok())
|
||||
.filter(|value| *value >= 1)
|
||||
.unwrap_or(1);
|
||||
let _ = send_mp_sent_page(ctx, msg, page).await;
|
||||
}
|
||||
|
||||
pub async fn handle_mpdelete(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(entry_id_raw) = args.first() else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+mpdelete <id>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(entry_id) = entry_id_raw.parse::<i64>() else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("ID invalide.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(entry) = get_sent_mp_message(&pool, bot_id, entry_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Message MP introuvable.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let delete_result = ChannelId::new(entry.dm_channel_id as u64)
|
||||
.delete_message(&ctx.http, MessageId::new(entry.message_id as u64))
|
||||
.await;
|
||||
|
||||
let _ = mark_sent_mp_deleted(&pool, bot_id, entry_id).await;
|
||||
if delete_result.is_err() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("MP déjà supprimé ou inaccessible")
|
||||
.description(format!(
|
||||
"Entrée `#{}` marquée supprimée en base (Discord a refusé la suppression).",
|
||||
entry.entry_id
|
||||
))
|
||||
.color(0xFEE75C);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
} else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("MP supprimé")
|
||||
.description(format!("Entrée `#{}` supprimée.", entry.entry_id))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_mp(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if args.len() < 2 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+mpsettings` ou `+mp <membre> <message>`")
|
||||
.description("Usage: `+mp <membre> <message>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
@@ -462,9 +447,9 @@ impl crate::commands::command_contract::CommandSpec for MpCommand {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "mp",
|
||||
category: "owner",
|
||||
params: "settings [on|off] | sent [page] | delete <id> | <@membre/ID> <message...>",
|
||||
description: "Permet de configurer, envoyer, lister et supprimer des messages prives envoyes.",
|
||||
examples: &["+mp", "+help mp"],
|
||||
params: "<@membre/ID> <message...>",
|
||||
description: "Envoie un message prive a un membre cible.",
|
||||
examples: &["+mp @Arthur Salut", "+help mp"],
|
||||
default_aliases: &["dmsg"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 9,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_mpdelete_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::mp::handle_mpdelete(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct MpdeleteCommand;
|
||||
pub static COMMAND_DESCRIPTOR: MpdeleteCommand = MpdeleteCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for MpdeleteCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "mpdelete",
|
||||
category: "owner",
|
||||
params: "<id>",
|
||||
description: "Supprime un MP envoye precedemment a partir de son identifiant interne.",
|
||||
examples: &["+mpdelete 12", "+mpdel 12", "+help mpdelete"],
|
||||
default_aliases: &["mpdel"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 9,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_mpsent_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::mp::handle_mpsent(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct MpsentCommand;
|
||||
pub static COMMAND_DESCRIPTOR: MpsentCommand = MpsentCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for MpsentCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "mpsent",
|
||||
category: "owner",
|
||||
params: "[page]",
|
||||
description: "Affiche l'historique des MP envoyes par le bot.",
|
||||
examples: &["+mpsent", "+mpsent 2", "+help mpsent"],
|
||||
default_aliases: &["mps"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 9,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_mpsettings_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::mp::handle_mpsettings(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct MpsettingsCommand;
|
||||
pub static COMMAND_DESCRIPTOR: MpsettingsCommand = MpsettingsCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for MpsettingsCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "mpsettings",
|
||||
category: "owner",
|
||||
params: "[on|off]",
|
||||
description: "Affiche ou modifie l'etat global de l'envoi de MP par le bot.",
|
||||
examples: &["+mpsettings", "+mpsettings off", "+help mpsettings"],
|
||||
default_aliases: &["mpset"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 9,
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
-26
@@ -18,7 +18,7 @@ pub async fn handle_alias(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
};
|
||||
|
||||
if args.len() == 1 {
|
||||
if args.is_empty() {
|
||||
let aliases = list_command_aliases(&pool, bot_id)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
@@ -44,28 +44,6 @@ pub async fn handle_alias(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if args[0].eq_ignore_ascii_case("remove") || args[0].eq_ignore_ascii_case("delete") {
|
||||
let alias_name = args[1].trim_start_matches('+').to_lowercase();
|
||||
if alias_name.is_empty() {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Alias invalide.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let removed = remove_command_alias(&pool, bot_id, &alias_name)
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Alias supprimé")
|
||||
.description(format!("`{}` : {} suppression(s).", alias_name, removed))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let command = args[0].trim_start_matches('+').to_lowercase();
|
||||
let is_known = all_command_keys().iter().any(|candidate| candidate == &command)
|
||||
|| crate::commands::command_metadata_by_key(&command).is_some();
|
||||
@@ -99,6 +77,46 @@ pub async fn handle_alias(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_unalias(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(raw_alias) = args.first() else {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+unalias <alias>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let alias_name = raw_alias.trim_start_matches('+').to_lowercase();
|
||||
if alias_name.is_empty() {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Alias invalide.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let removed = remove_command_alias(&pool, bot_id, &alias_name)
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Alias supprimé")
|
||||
.description(format!("`{}` : {} suppression(s).", alias_name, removed))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
@@ -124,9 +142,9 @@ impl crate::commands::command_contract::CommandSpec for AliasCommand {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "alias",
|
||||
category: "perms",
|
||||
params: "<commande> <alias> | remove <alias> | list",
|
||||
description: "Liste, ajoute ou supprime des aliases de commandes stockes en base.",
|
||||
examples: &["+alias", "+as", "+help alias"],
|
||||
params: "[<commande> <alias>]",
|
||||
description: "Liste les aliases (sans argument) ou ajoute un alias de commande.",
|
||||
examples: &["+alias", "+alias mute m", "+help alias"],
|
||||
default_aliases: &["als"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 6,
|
||||
|
||||
@@ -11,16 +11,16 @@ pub async fn handle_del(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if args.len() < 2 || !args[0].eq_ignore_ascii_case("perm") {
|
||||
if args.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+delperm <role>`")
|
||||
.description("Usage: `+delperm <role/membre>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let Some((scope_type, scope_id)) = parse_user_or_role(args[1]) else {
|
||||
let Some((scope_type, scope_id)) = parse_user_or_role(args[0]) else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Role/membre invalide.")
|
||||
@@ -50,20 +50,3 @@ pub async fn handle_del(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub struct DelCommand;
|
||||
pub static COMMAND_DESCRIPTOR: DelCommand = DelCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for DelCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "del",
|
||||
category: "perms",
|
||||
params: "perm <@&rôle/@membre/ID>",
|
||||
description: "Supprime les permissions ACL associees a un role ou utilisateur.",
|
||||
examples: &["+del", "+dl", "+help del"],
|
||||
default_aliases: &["dlp"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_delperm_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::del::handle_del(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct DelpermCommand;
|
||||
pub static COMMAND_DESCRIPTOR: DelpermCommand = DelpermCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for DelpermCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "delperm",
|
||||
category: "perms",
|
||||
params: "<@&role/@membre/ID>",
|
||||
description: "Supprime les permissions ACL associees a un role ou utilisateur.",
|
||||
examples: &["+delperm @Role", "+help delperm"],
|
||||
default_aliases: &["dlp"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
-52
@@ -133,8 +133,8 @@ fn help_page_for_command(
|
||||
) -> &'static str {
|
||||
match meta.name {
|
||||
"modlog" | "messagelog" | "voicelog" | "boostlog" | "rolelog" | "raidlog"
|
||||
| "autoconfiglog" | "nolog" | "join" | "boostembed" | "setmodlogs" | "setboostembed"
|
||||
| "leavesettings" | "viewlogs" => "logs",
|
||||
| "autoconfiglog" | "nolog" | "joinsettings" | "boostembed" | "setmodlogs"
|
||||
| "setboostembed" | "leavesettings" | "viewlogs" => "logs",
|
||||
"warn"
|
||||
| "mute"
|
||||
| "tempmute"
|
||||
@@ -164,14 +164,19 @@ fn help_page_for_command(
|
||||
"giveaway" | "end" | "reroll" | "choose" | "calc" | "emoji" | "embed" | "say"
|
||||
| "create" | "newsticker" | "button" | "autoreact" | "snipe" | "loading" | "backup"
|
||||
| "autobackup" => "outils",
|
||||
"shadowbot" | "set" | "theme" | "playto" | "listen" | "watch" | "compet" | "stream"
|
||||
| "removeactivity" | "online" | "idle" | "dnd" | "invisible" | "change" | "changeall" => {
|
||||
"shadowbot" | "setname" | "setpic" | "setbanner" | "setprofil" | "setperm"
|
||||
| "theme" | "playto" | "listen" | "watch" | "compet" | "stream"
|
||||
| "removeactivity" | "online" | "idle" | "dnd" | "invisible" | "change"
|
||||
| "changereset" | "changeall" => {
|
||||
"bot"
|
||||
}
|
||||
"owner" | "unowner" | "clearowners" | "bl" | "unbl" | "blinfo" | "clearbl"
|
||||
| "allbots" | "alladmins" | "botadmins" | "mainprefix" | "prefix" | "mp" | "invite"
|
||||
| "leave" | "discussion" => "administration",
|
||||
"perms" | "del" | "clearperms" | "allperms" | "alias" | "help" | "helpsetting" => {
|
||||
| "allbots" | "alladmins" | "botadmins" | "mainprefix" | "prefix" | "mp"
|
||||
| "mpsettings" | "mpsent" | "mpdelete" | "invite" | "leave" | "discussion" => {
|
||||
"administration"
|
||||
}
|
||||
"perms" | "delperm" | "clearperms" | "allperms" | "alias" | "help"
|
||||
| "helpsetting" => {
|
||||
"permissions"
|
||||
}
|
||||
_ => match meta.category {
|
||||
@@ -193,6 +198,7 @@ fn help_page_for_command(
|
||||
"administration" => "administration",
|
||||
"permissions" => "permissions",
|
||||
"general" => "infos",
|
||||
"automation" => "outils",
|
||||
"profile" => "bot",
|
||||
"admin" => "administration",
|
||||
_ => "infos",
|
||||
@@ -332,48 +338,6 @@ async fn aliases_map(ctx: &Context) -> BTreeMap<String, Vec<String>> {
|
||||
|
||||
fn command_doc(key: &str) -> Option<CommandDoc> {
|
||||
let (meta, command, acl_key, alias_source_key) = match key {
|
||||
"mpsettings" => (
|
||||
crate::commands::command_metadata_by_key("mp")?,
|
||||
"mpsettings",
|
||||
"mpsettings",
|
||||
Some("mp"),
|
||||
),
|
||||
"mpsent" => (
|
||||
crate::commands::command_metadata_by_key("mp")?,
|
||||
"mpsent",
|
||||
"mpsent",
|
||||
Some("mp"),
|
||||
),
|
||||
"mpdelete" => (
|
||||
crate::commands::command_metadata_by_key("mp")?,
|
||||
"mpdelete",
|
||||
"mpdelete",
|
||||
Some("mp"),
|
||||
),
|
||||
"serverlist" => (
|
||||
crate::commands::command_metadata_by_key("server")?,
|
||||
"serverlist",
|
||||
"serverlist",
|
||||
Some("server"),
|
||||
),
|
||||
"changereset" => (
|
||||
crate::commands::command_metadata_by_key("change")?,
|
||||
"changereset",
|
||||
"changereset",
|
||||
Some("change"),
|
||||
),
|
||||
"setperm" => (
|
||||
crate::commands::command_metadata_by_key("set")?,
|
||||
"setperm",
|
||||
"setperm",
|
||||
Some("set"),
|
||||
),
|
||||
"delperm" => (
|
||||
crate::commands::command_metadata_by_key("del")?,
|
||||
"delperm",
|
||||
"delperm",
|
||||
Some("del"),
|
||||
),
|
||||
other => {
|
||||
let meta = crate::commands::command_metadata_by_key(other)?;
|
||||
(meta, meta.name, meta.name, Some(meta.name))
|
||||
@@ -423,22 +387,37 @@ fn help_lookup_to_key(input: &str) -> Option<&'static str> {
|
||||
"member" => Some("member"),
|
||||
"pic" => Some("pic"),
|
||||
"banner" => Some("banner"),
|
||||
"server" => Some("server"),
|
||||
"server" | "server list" => Some("serverlist"),
|
||||
"server pic" | "serverpic" => Some("serverpic"),
|
||||
"server banner" | "serverbanner" => Some("serverbanner"),
|
||||
"serverlist" => Some("serverlist"),
|
||||
"suggestion settings" | "suggestionsettings" => Some("suggestionsettings"),
|
||||
"snipe" => Some("snipe"),
|
||||
"emoji" => Some("emoji"),
|
||||
"giveaway" => Some("giveaway"),
|
||||
"end" | "endgiveaway" => Some("end"),
|
||||
"end" => Some("end"),
|
||||
"end giveaway" | "endgiveaway" => Some("endgiveaway"),
|
||||
"reroll" => Some("reroll"),
|
||||
"choose" => Some("choose"),
|
||||
"embed" => Some("embed"),
|
||||
"backup" | "backup list" | "backup delete" | "backup load" => Some("backup"),
|
||||
"autopublish" => Some("autopublish"),
|
||||
"autopublish on" | "autopublishon" => Some("autopublishon"),
|
||||
"autopublish off" | "autopublishoff" => Some("autopublishoff"),
|
||||
"autobackup" => Some("autobackup"),
|
||||
"loading" => Some("loading"),
|
||||
"create" => Some("create"),
|
||||
"newsticker" => Some("newsticker"),
|
||||
"piconly" => Some("piconly"),
|
||||
"piconly add" | "piconlyadd" => Some("piconlyadd"),
|
||||
"piconly del" | "piconly remove" | "piconly delete" | "piconlydel" => {
|
||||
Some("piconlydel")
|
||||
}
|
||||
"massiverole" => Some("massiverole"),
|
||||
"unmassiverole" => Some("unmassiverole"),
|
||||
"noderank" => Some("noderank"),
|
||||
"noderank add" | "noderankadd" => Some("noderankadd"),
|
||||
"noderank del" | "noderank remove" | "noderankdel" => Some("noderankdel"),
|
||||
"voicemove" => Some("voicemove"),
|
||||
"voicekick" => Some("voicekick"),
|
||||
"cleanup" => Some("cleanup"),
|
||||
@@ -452,7 +431,10 @@ fn help_lookup_to_key(input: &str) -> Option<&'static str> {
|
||||
"autoreact" => Some("autoreact"),
|
||||
"calc" => Some("calc"),
|
||||
"shadowbot" => Some("shadowbot"),
|
||||
"set" => Some("set"),
|
||||
"set" | "set name" | "setname" => Some("setname"),
|
||||
"set pic" | "setpic" => Some("setpic"),
|
||||
"set banner" | "setbanner" => Some("setbanner"),
|
||||
"set profil" | "setprofil" => Some("setprofil"),
|
||||
"theme" => Some("theme"),
|
||||
"playto" => Some("playto"),
|
||||
"listen" => Some("listen"),
|
||||
@@ -479,6 +461,7 @@ fn help_lookup_to_key(input: &str) -> Option<&'static str> {
|
||||
"say" => Some("say"),
|
||||
"invite" => Some("invite"),
|
||||
"leave" => Some("leave"),
|
||||
"join" | "join settings" | "joinsettings" => Some("joinsettings"),
|
||||
"change" => Some("change"),
|
||||
"changereset" => Some("changereset"),
|
||||
"changeall" => Some("changeall"),
|
||||
@@ -488,8 +471,13 @@ fn help_lookup_to_key(input: &str) -> Option<&'static str> {
|
||||
"allperms" => Some("allperms"),
|
||||
"setperm" => Some("setperm"),
|
||||
"delperm" => Some("delperm"),
|
||||
"punish" => Some("punish"),
|
||||
"punish setup" | "punishsetup" => Some("punishsetup"),
|
||||
"punish add" | "punishadd" => Some("punishadd"),
|
||||
"punish del" | "punishdel" => Some("punishdel"),
|
||||
"clearperms" => Some("clearperms"),
|
||||
"alias" => Some("alias"),
|
||||
"alias remove" | "alias delete" | "unalias" => Some("unalias"),
|
||||
"helpsetting" | "helpetting" => Some("helpsetting"),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_unalias_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::alias::handle_unalias(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct UnaliasCommand;
|
||||
pub static COMMAND_DESCRIPTOR: UnaliasCommand = UnaliasCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for UnaliasCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "unalias",
|
||||
category: "perms",
|
||||
params: "<alias>",
|
||||
description: "Supprime un alias de commande en base.",
|
||||
examples: &["+unalias m", "+help unalias"],
|
||||
default_aliases: &["uals"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 6,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::db::DbPoolKey;
|
||||
use crate::permissions::is_owner_user;
|
||||
|
||||
pub fn parse_user_or_role(input: &str) -> Option<(&'static str, u64)> {
|
||||
let trimmed = input.trim();
|
||||
if trimmed.starts_with("<@&") && trimmed.ends_with('>') {
|
||||
return trimmed
|
||||
.trim_start_matches("<@&")
|
||||
.trim_end_matches('>')
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.map(|id| ("role", id));
|
||||
}
|
||||
|
||||
if (trimmed.starts_with("<@") && trimmed.ends_with('>')) || trimmed.parse::<u64>().is_ok() {
|
||||
let cleaned = trimmed
|
||||
.trim_start_matches('<')
|
||||
.trim_end_matches('>')
|
||||
.trim_start_matches('@')
|
||||
.trim_start_matches('!');
|
||||
if let Ok(id) = cleaned.parse::<u64>() {
|
||||
return Some(("user", id));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn normalize_command_name(input: &str) -> String {
|
||||
let normalized = input.trim_start_matches('+').to_lowercase();
|
||||
let underscored = normalized
|
||||
.split_whitespace()
|
||||
.collect::<Vec<_>>()
|
||||
.join("_");
|
||||
let compact = underscored.replace('_', "");
|
||||
|
||||
let known = crate::permissions::all_command_keys();
|
||||
|
||||
let direct = crate::permissions::command_key(&underscored, &[]);
|
||||
if known.iter().any(|value| value == &direct) {
|
||||
return direct;
|
||||
}
|
||||
|
||||
let compact_mapped = crate::permissions::command_key(&compact, &[]);
|
||||
if known.iter().any(|value| value == &compact_mapped) {
|
||||
return compact_mapped;
|
||||
}
|
||||
|
||||
underscored
|
||||
}
|
||||
|
||||
pub async fn ensure_owner(ctx: &Context, msg: &Message) -> bool {
|
||||
if is_owner_user(ctx, msg.author.id).await {
|
||||
true
|
||||
} else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Acces refuse")
|
||||
.description("Commande reservee aux owners.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_pool(ctx: &Context) -> Option<PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
use serenity::builder::{
|
||||
CreateActionRow, CreateButton, CreateEmbed, CreateInteractionResponse,
|
||||
CreateInteractionResponseMessage,
|
||||
};
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{theme_color, truncate_text};
|
||||
use crate::permissions::{all_command_keys, command_required_permission, default_permission};
|
||||
|
||||
const ALLPERMS_PAGE_SIZE: usize = 12;
|
||||
const ALLPERMS_CUSTOM_ID_PREFIX: &str = "allperms";
|
||||
|
||||
pub async fn handle_allperms_component(ctx: &Context, component: &ComponentInteraction) -> bool {
|
||||
let Some((owner_id, requested_page)) = parse_allperms_custom_id(&component.data.custom_id)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if component.user.id.get() != owner_id {
|
||||
let _ = component
|
||||
.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::Message(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.content("Seul l'auteur de la commande peut utiliser ces boutons.")
|
||||
.ephemeral(true),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
|
||||
let lines = collect_allperms_lines(ctx).await;
|
||||
let total_pages = total_pages_for(lines.len());
|
||||
let page = requested_page.min(total_pages.saturating_sub(1));
|
||||
let color = theme_color(ctx).await;
|
||||
let embed = build_allperms_embed(&lines, page, color);
|
||||
let components = allperms_components(component.user.id, page, total_pages);
|
||||
|
||||
let _ = component
|
||||
.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::UpdateMessage(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.embed(embed)
|
||||
.components(components),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
async fn collect_allperms_lines(ctx: &Context) -> Vec<String> {
|
||||
let mut commands = all_command_keys();
|
||||
if !commands.iter().any(|c| c == "allperms") {
|
||||
commands.push("allperms".to_string());
|
||||
}
|
||||
commands.sort();
|
||||
|
||||
let mut lines = Vec::with_capacity(commands.len());
|
||||
for cmd in commands {
|
||||
let required = command_required_permission(ctx, &cmd).await;
|
||||
let default = default_permission(&cmd);
|
||||
|
||||
if required == default {
|
||||
lines.push(format!("`{}` -> `{}`", cmd, required));
|
||||
} else {
|
||||
lines.push(format!(
|
||||
"`{}` -> `{}` (defaut `{}`)",
|
||||
cmd, required, default
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
lines
|
||||
}
|
||||
|
||||
fn total_pages_for(total_items: usize) -> usize {
|
||||
((total_items + ALLPERMS_PAGE_SIZE.saturating_sub(1)) / ALLPERMS_PAGE_SIZE).max(1)
|
||||
}
|
||||
|
||||
fn build_allperms_embed(lines: &[String], page: usize, color: u32) -> CreateEmbed {
|
||||
let total_pages = total_pages_for(lines.len());
|
||||
let safe_page = page.min(total_pages.saturating_sub(1));
|
||||
let start = safe_page * ALLPERMS_PAGE_SIZE;
|
||||
let end = (start + ALLPERMS_PAGE_SIZE).min(lines.len());
|
||||
let chunk = if start < end { &lines[start..end] } else { &[] };
|
||||
|
||||
let value = if chunk.is_empty() {
|
||||
"Aucune commande.".to_string()
|
||||
} else {
|
||||
truncate_text(&chunk.join("\n"), 1024)
|
||||
};
|
||||
|
||||
CreateEmbed::new()
|
||||
.title("Permissions de toutes les commandes")
|
||||
.description(format!(
|
||||
"{} commande(s) · Page {}/{}",
|
||||
lines.len(),
|
||||
safe_page + 1,
|
||||
total_pages
|
||||
))
|
||||
.field("Niveaux requis", value, false)
|
||||
.color(color)
|
||||
}
|
||||
|
||||
fn allperms_components(owner_id: UserId, page: usize, total_pages: usize) -> Vec<CreateActionRow> {
|
||||
let safe_total = total_pages.max(1);
|
||||
let safe_page = page.min(safe_total.saturating_sub(1));
|
||||
let prev_page = safe_page.saturating_sub(1);
|
||||
let next_page = (safe_page + 1).min(safe_total.saturating_sub(1));
|
||||
|
||||
vec![CreateActionRow::Buttons(vec![
|
||||
CreateButton::new(format!(
|
||||
"{}:{}:{}",
|
||||
ALLPERMS_CUSTOM_ID_PREFIX,
|
||||
owner_id.get(),
|
||||
prev_page
|
||||
))
|
||||
.label("◀ Precedent")
|
||||
.style(ButtonStyle::Primary)
|
||||
.disabled(safe_page == 0),
|
||||
CreateButton::new(format!(
|
||||
"{}:{}:{}",
|
||||
ALLPERMS_CUSTOM_ID_PREFIX,
|
||||
owner_id.get(),
|
||||
next_page
|
||||
))
|
||||
.label("Suivant ▶")
|
||||
.style(ButtonStyle::Primary)
|
||||
.disabled(safe_page + 1 >= safe_total),
|
||||
])]
|
||||
}
|
||||
|
||||
fn parse_allperms_custom_id(custom_id: &str) -> Option<(u64, usize)> {
|
||||
let mut parts = custom_id.split(':');
|
||||
let prefix = parts.next()?;
|
||||
if prefix != ALLPERMS_CUSTOM_ID_PREFIX {
|
||||
return None;
|
||||
}
|
||||
|
||||
let owner_id = parts.next()?.parse::<u64>().ok()?;
|
||||
let page = parts.next()?.parse::<usize>().ok()?;
|
||||
Some((owner_id, page))
|
||||
}
|
||||
+106
-30
@@ -17,69 +17,145 @@ pub async fn handle_noderank(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
|
||||
if args.is_empty() {
|
||||
let roles = db::list_noderank_roles(&pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let description = if roles.is_empty() {
|
||||
"Aucun role protege.".to_string()
|
||||
} else {
|
||||
roles
|
||||
.iter()
|
||||
.map(|role_id| format!("<@&{}>", role_id))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
if !args.is_empty() {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("NoDeRank")
|
||||
.description(description)
|
||||
.color(0x5865F2),
|
||||
.description("Utilisation: +noderank")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args.len() < 2 {
|
||||
let roles = db::list_noderank_roles(&pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let description = if roles.is_empty() {
|
||||
"Aucun role protege.".to_string()
|
||||
} else {
|
||||
roles
|
||||
.iter()
|
||||
.map(|role_id| format!("<@&{}>", role_id))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("NoDeRank")
|
||||
.description(description)
|
||||
.color(0x5865F2),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_noderankadd(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if args.is_empty() {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("NoDeRank")
|
||||
.description("Utilisation: +noderankadd <@role/ID/nom>")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
|
||||
let Ok(guild) = guild_id.to_partial_guild(&ctx.http).await else {
|
||||
return;
|
||||
};
|
||||
let Some(role) = parse_role(&guild, args[1]) else {
|
||||
let Some(role) = parse_role(&guild, args[0]) else {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("NoDeRank")
|
||||
.description("Role introuvable.")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
if args[0].eq_ignore_ascii_case("add") {
|
||||
let _ = db::add_noderank_role(&pool, bot_id, guild_id_raw, role.id.get() as i64).await;
|
||||
let _ = db::add_noderank_role(&pool, bot_id, guild_id_raw, role.id.get() as i64).await;
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("NoDeRank")
|
||||
.description(format!("Role protege ajoute: <@&{}>", role.id.get()))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_noderankdel(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if args.is_empty() {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("NoDeRank")
|
||||
.description(format!("Role protege ajoute: <@&{}>", role.id.get()))
|
||||
.color(0x57F287),
|
||||
.description("Utilisation: +noderankdel <@role/ID/nom>")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args[0].eq_ignore_ascii_case("del") || args[0].eq_ignore_ascii_case("remove") {
|
||||
let _ = db::remove_noderank_role(&pool, bot_id, guild_id_raw, role.id.get() as i64).await;
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
|
||||
let Ok(guild) = guild_id.to_partial_guild(&ctx.http).await else {
|
||||
return;
|
||||
};
|
||||
let Some(role) = parse_role(&guild, args[0]) else {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("NoDeRank")
|
||||
.description(format!("Role protege retire: <@&{}>", role.id.get()))
|
||||
.color(0x57F287),
|
||||
.description("Role introuvable.")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
let _ = db::remove_noderank_role(&pool, bot_id, guild_id_raw, role.id.get() as i64).await;
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("NoDeRank")
|
||||
.description(format!("Role protege retire: <@&{}>", role.id.get()))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct NoderankCommand;
|
||||
@@ -90,9 +166,9 @@ impl crate::commands::command_contract::CommandSpec for NoderankCommand {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "noderank",
|
||||
category: "roles",
|
||||
params: "[add/del <@role/ID/nom>]",
|
||||
description: "Definit des roles proteges qui ne sont pas retires par +derank.",
|
||||
examples: &["+noderank", "+noderank add @VIP", "+noderank del @VIP"],
|
||||
params: "aucun",
|
||||
description: "Affiche la liste des roles proteges qui ne sont pas retires par +derank.",
|
||||
examples: &["+noderank", "+help noderank"],
|
||||
default_aliases: &["ndr"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 7,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_noderankadd_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::noderank::handle_noderankadd(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct NoderankaddCommand;
|
||||
pub static COMMAND_DESCRIPTOR: NoderankaddCommand = NoderankaddCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for NoderankaddCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "noderankadd",
|
||||
category: "roles",
|
||||
params: "<@role/ID/nom>",
|
||||
description: "Ajoute un role a la liste NoDeRank.",
|
||||
examples: &["+noderankadd @VIP", "+help noderankadd"],
|
||||
default_aliases: &["ndra"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
pub async fn handle_noderankdel_command(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
crate::commands::noderank::handle_noderankdel(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct NoderankdelCommand;
|
||||
pub static COMMAND_DESCRIPTOR: NoderankdelCommand = NoderankdelCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for NoderankdelCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "noderankdel",
|
||||
category: "roles",
|
||||
params: "<@role/ID/nom>",
|
||||
description: "Retire un role de la liste NoDeRank.",
|
||||
examples: &["+noderankdel @VIP", "+help noderankdel"],
|
||||
default_aliases: &["ndrd"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Gestion centralisée des slash commands
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
use crate::commands::{autopublish, showpics, suggestion, tempvoc, ticket, tickets};
|
||||
|
||||
pub async fn handle_slash_commands(ctx: &Context, interaction: &Interaction) -> bool {
|
||||
if ticket::handle_slash_interaction(ctx, interaction).await {
|
||||
return true;
|
||||
}
|
||||
|
||||
if tickets::handle_slash_interaction(ctx, interaction).await {
|
||||
return true;
|
||||
}
|
||||
|
||||
if showpics::handle_slash_interaction(ctx, interaction).await {
|
||||
return true;
|
||||
}
|
||||
|
||||
if suggestion::handle_slash_interaction(ctx, interaction).await {
|
||||
return true;
|
||||
}
|
||||
|
||||
if autopublish::handle_slash_interaction(ctx, interaction).await {
|
||||
return true;
|
||||
}
|
||||
|
||||
if tempvoc::handle_slash_interaction(ctx, interaction).await {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
Reference in New Issue
Block a user