clean dossier commands et correction quelque commands

This commit is contained in:
Puechberty Arthur
2026-04-10 02:40:26 +02:00
parent 3e69185296
commit 572cfa17b2
139 changed files with 687 additions and 195 deletions
+27
View File
@@ -0,0 +1,27 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_addrole(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_add_del_role(ctx, msg, args, true).await;
}
pub struct AddroleCommand;
pub static COMMAND_DESCRIPTOR: AddroleCommand = AddroleCommand;
impl crate::commands::command_contract::CommandSpec for AddroleCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "addrole",
command: "addrole",
category: "admin",
params: "<@membre/ID[,..]> <@role/ID>",
summary: "Ajoute un role",
description: "Ajoute un role a un ou plusieurs membres.",
examples: &["+addrole @User @Membre"],
alias_source_key: "addrole",
default_aliases: &["ar"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_autobackup(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_autobackup(ctx, msg, args).await;
}
pub struct AutoBackupCommand;
pub static COMMAND_DESCRIPTOR: AutoBackupCommand = AutoBackupCommand;
impl crate::commands::command_contract::CommandSpec for AutoBackupCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "autobackup",
command: "autobackup",
category: "admin",
params: "<serveur/emoji> <jours>",
summary: "Configure les backups automatiques",
description: "Definit l'intervalle en jours des backups automatiques.",
examples: &["+autobackup serveur 3", "+autobackup emoji 7"],
alias_source_key: "autobackup",
default_aliases: &["abkp"],
default_permission: 8,
}
}
}
+27
View File
@@ -0,0 +1,27 @@
use crate::commands::logs_service;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_autoconfiglog(ctx: &Context, msg: &Message) {
logs_service::handle_autoconfiglog(ctx, msg).await;
}
pub struct AutoconfiglogCommand;
pub static COMMAND_DESCRIPTOR: AutoconfiglogCommand = AutoconfiglogCommand;
impl crate::commands::command_contract::CommandSpec for AutoconfiglogCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "autoconfiglog",
command: "autoconfiglog",
category: "admin",
params: "aucun",
summary: "Cree tous les salons de logs",
description: "Cree automatiquement les salons de logs et les configure.",
examples: &["+autoconfiglog"],
alias_source_key: "autoconfiglog",
default_aliases: &["acl"],
default_permission: 8,
}
}
}
+112
View File
@@ -0,0 +1,112 @@
use chrono::Utc;
use serenity::builder::CreateEmbed;
use serenity::model::Colour;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::common::{parse_channel_id, send_embed};
use crate::db;
pub async fn handle_autopublish(ctx: &Context, msg: &Message, args: &[&str]) {
let Some(guild_id) = msg.guild_id else {
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")
};
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]")
.color(0xED4245),
)
.await;
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)
.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
};
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;
}
let embed = if enabled {
CreateEmbed::new()
.title("Autopublish activé")
.description(format!("Salon: <#{}>", channel_id.get()))
.colour(Colour::from_rgb(0, 200, 120))
.timestamp(Utc::now())
} else {
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;
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_autoreact(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_autoreact(ctx, msg, args).await;
}
pub struct AutoReactCommand;
pub static COMMAND_DESCRIPTOR: AutoReactCommand = AutoReactCommand;
impl crate::commands::command_contract::CommandSpec for AutoReactCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "autoreact",
command: "autoreact",
category: "admin",
params: "<add/del> <salon> <emoji> | list",
summary: "Configure les reactions automatiques",
description: "Ajoute, retire et liste les reactions automatiquement appliquees aux messages d'un salon.",
examples: &["+autoreact add #general 😀", "+autoreact list"],
alias_source_key: "autoreact",
default_aliases: &["ar", "reactauto"],
default_permission: 8,
}
}
}
+32
View File
@@ -0,0 +1,32 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_backup(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_backup(ctx, msg, args).await;
}
pub struct BackupCommand;
pub static COMMAND_DESCRIPTOR: BackupCommand = BackupCommand;
impl crate::commands::command_contract::CommandSpec for BackupCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "backup",
command: "backup",
category: "admin",
params: "<serveur/emoji> <nom> | list/delete/load",
summary: "Gere les backups serveur et emojis",
description: "Cree, liste, supprime et recharge des backups serveur ou emojis.",
examples: &[
"+backup serveur prod_1",
"+backup list serveur",
"+backup load emoji nightly",
],
alias_source_key: "backup",
default_aliases: &["bkp"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_ban(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_ban(ctx, msg, args, false).await;
}
pub struct BanCommand;
pub static COMMAND_DESCRIPTOR: BanCommand = BanCommand;
impl crate::commands::command_contract::CommandSpec for BanCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "ban",
command: "ban",
category: "admin",
params: "<@membre/ID[,..]> [raison]",
summary: "Bannit un membre",
description: "Ban un ou plusieurs membres.",
examples: &["+ban @User"],
alias_source_key: "ban",
default_aliases: &["b"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_banlist(ctx: &Context, msg: &Message) {
moderation_tools::handle_banlist(ctx, msg).await;
}
pub struct BanlistCommand;
pub static COMMAND_DESCRIPTOR: BanlistCommand = BanlistCommand;
impl crate::commands::command_contract::CommandSpec for BanlistCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "banlist",
command: "banlist",
category: "admin",
params: "aucun",
summary: "Liste les bans",
description: "Affiche la liste des bannissements en cours.",
examples: &["+banlist"],
alias_source_key: "banlist",
default_aliases: &["bls"],
default_permission: 8,
}
}
}
+93
View File
@@ -0,0 +1,93 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::admin_common::{ban_user_everywhere, ensure_owner, parse_user_id};
use crate::commands::common::{add_list_fields, send_embed, theme_color, truncate_text};
use crate::db::{DbPoolKey, add_to_blacklist, list_blacklist};
pub async fn handle_bl(ctx: &Context, msg: &Message, args: &[&str]) {
if ensure_owner(ctx, msg).await.is_err() {
return;
}
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 {
let embed = serenity::builder::CreateEmbed::new()
.title("Erreur")
.description("DB indisponible.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
if args.is_empty() {
let rows = list_blacklist(&pool, bot_id).await.unwrap_or_default();
let lines = rows
.iter()
.map(|r| format!("<@{}> · {}", r.user_id, truncate_text(&r.reason, 80)))
.collect::<Vec<_>>();
let color = theme_color(ctx).await;
let mut embed = serenity::builder::CreateEmbed::new()
.title("Blacklist")
.color(color);
embed = add_list_fields(embed, &lines, "Membres blacklistés");
send_embed(ctx, msg, embed).await;
return;
}
let Some(target) = parse_user_id(args[0]) else {
let embed = serenity::builder::CreateEmbed::new()
.title("Erreur")
.description("Membre invalide.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let reason = if args.len() > 1 {
args[1..].join(" ")
} else {
"Aucune raison fournie".to_string()
};
let _ = add_to_blacklist(&pool, bot_id, target, &reason, Some(msg.author.id)).await;
let (ok, ko) = ban_user_everywhere(ctx, target, &format!("Blacklist: {}", reason)).await;
let embed = serenity::builder::CreateEmbed::new()
.title("Blacklist mise à jour")
.description(format!("<@{}> a été blacklisté.", target.get()))
.field("Raison", truncate_text(&reason, 1024), false)
.field(
"Bans appliqués",
format!("{} réussis · {} échecs", ok, ko),
false,
)
.color(0x57F287);
send_embed(ctx, msg, embed).await;
}
pub struct BlCommand;
pub static COMMAND_DESCRIPTOR: BlCommand = BlCommand;
impl crate::commands::command_contract::CommandSpec for BlCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "bl",
command: "bl",
category: "admin",
params: "[<@membre/ID> [raison...]]",
summary: "Gere la blacklist globale",
description: "Affiche la blacklist ou ajoute un utilisateur a la blacklist globale du bot.",
examples: &["+bl", "+help bl"],
alias_source_key: "bl",
default_aliases: &["bls"],
default_permission: 9,
}
}
}
+98
View File
@@ -0,0 +1,98 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::admin_common::{ensure_owner, parse_user_id};
use crate::commands::common::{send_embed, truncate_text};
use crate::db::{DbPoolKey, get_blacklist_info};
pub async fn handle_blinfo(ctx: &Context, msg: &Message, args: &[&str]) {
if ensure_owner(ctx, msg).await.is_err() {
return;
}
if args.is_empty() {
let embed = serenity::builder::CreateEmbed::new()
.title("Erreur")
.description("Usage: `+blinfo <@membre/ID>`")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
}
let Some(target) = parse_user_id(args[0]) else {
let embed = serenity::builder::CreateEmbed::new()
.title("Erreur")
.description("Membre invalide.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
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 {
let embed = serenity::builder::CreateEmbed::new()
.title("Erreur")
.description("DB indisponible.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let info = get_blacklist_info(&pool, bot_id, target)
.await
.ok()
.flatten();
let Some(info) = info else {
let embed = serenity::builder::CreateEmbed::new()
.title("Blacklist")
.description("Ce membre n'est pas blacklisté.")
.color(0xFF0000);
send_embed(ctx, msg, embed).await;
return;
};
let added_at = crate::commands::common::discord_ts(
Timestamp::from_unix_timestamp(info.added_at.timestamp())
.unwrap_or_else(|_| Timestamp::now()),
"F",
);
let by = info
.added_by
.map(|id| format!("<@{}>", id))
.unwrap_or_else(|| "Inconnu".to_string());
let embed = serenity::builder::CreateEmbed::new()
.title("Informations blacklist")
.field("Membre", format!("<@{}>", info.user_id), true)
.field("Ajouté par", by, true)
.field("Ajouté le", added_at, true)
.field("Raison", truncate_text(&info.reason, 1024), false)
.color(0xFF0000);
send_embed(ctx, msg, embed).await;
}
pub struct BlinfoCommand;
pub static COMMAND_DESCRIPTOR: BlinfoCommand = BlinfoCommand;
impl crate::commands::command_contract::CommandSpec for BlinfoCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "blinfo",
command: "blinfo",
category: "admin",
params: "<@membre/ID>",
summary: "Affiche les details blacklist",
description: "Affiche les details de blacklist pour un utilisateur donne.",
examples: &["+blinfo", "+bo", "+help blinfo"],
alias_source_key: "blinfo",
default_aliases: &["bli"],
default_permission: 9,
}
}
}
+27
View File
@@ -0,0 +1,27 @@
use crate::commands::logs_service;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_boostembed(ctx: &Context, msg: &Message, args: &[&str]) {
logs_service::handle_boostembed(ctx, msg, args).await;
}
pub struct BoostembedCommand;
pub static COMMAND_DESCRIPTOR: BoostembedCommand = BoostembedCommand;
impl crate::commands::command_contract::CommandSpec for BoostembedCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "boostembed",
command: "boostembed",
category: "admin",
params: "<on|off|test>",
summary: "Active, coupe ou teste l embed boost",
description: "Controle l embed de boost et permet un test rapide.",
examples: &["+boostembed on", "+boostembed test"],
alias_source_key: "boostembed",
default_aliases: &["bembed"],
default_permission: 8,
}
}
}
+27
View File
@@ -0,0 +1,27 @@
use crate::commands::logs_service;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_boostlog(ctx: &Context, msg: &Message, args: &[&str]) {
logs_service::handle_log_toggle(ctx, msg, args, "boost", "BoostLog").await;
}
pub struct BoostlogCommand;
pub static COMMAND_DESCRIPTOR: BoostlogCommand = BoostlogCommand;
impl crate::commands::command_contract::CommandSpec for BoostlogCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "boostlog",
command: "boostlog",
category: "admin",
params: "<on [salon]|off>",
summary: "Active les logs de boosts",
description: "Active ou desactive les logs de boosts.",
examples: &["+boostlog on #logs", "+boostlog off"],
alias_source_key: "boostlog",
default_aliases: &["blog"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_bringall(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_bringall(ctx, msg, args).await;
}
pub struct BringAllCommand;
pub static COMMAND_DESCRIPTOR: BringAllCommand = BringAllCommand;
impl crate::commands::command_contract::CommandSpec for BringAllCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "bringall",
command: "bringall",
category: "admin",
params: "[salon_vocal_destination]",
summary: "Rassemble tous les vocaux",
description: "Deplace tous les membres actuellement en vocal vers un salon cible.",
examples: &["+bringall #Event", "+bringall"],
alias_source_key: "bringall",
default_aliases: &["ball", "vbring"],
default_permission: 8,
}
}
}
+31
View File
@@ -0,0 +1,31 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_button(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_button(ctx, msg, args).await;
}
pub struct ButtonCommand;
pub static COMMAND_DESCRIPTOR: ButtonCommand = ButtonCommand;
impl crate::commands::command_contract::CommandSpec for ButtonCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "button",
command: "button",
category: "admin",
params: "<add/del> <lien>",
summary: "Gere des boutons decoratifs",
description: "Ajoute ou supprime un bouton de decoration personnalise sur un message du bot.",
examples: &[
"+button add https://example.com",
"+button del https://example.com",
],
alias_source_key: "button",
default_aliases: &["btn"],
default_permission: 8,
}
}
}
+69
View File
@@ -0,0 +1,69 @@
use chrono::Utc;
use serenity::builder::CreateEmbed;
use serenity::model::Colour;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::common::send_embed;
use crate::db;
pub async fn handle_claim(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 = msg.channel_id.get() as i64;
let Some(ticket) = db::get_ticket_by_channel(&pool, bot_id, guild_id_i64, channel_id)
.await
.ok()
.flatten()
else {
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Erreur")
.description("Ce salon n'est pas reconnu comme un ticket.")
.color(0xED4245),
)
.await;
return;
};
if db::claim_ticket(&pool, ticket.id, msg.author.id.get() as i64)
.await
.is_err()
{
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Erreur")
.description("Impossible de revendiquer ce ticket.")
.color(0xED4245),
)
.await;
return;
}
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Ticket revendiqué")
.description(format!("Le ticket #{} a été revendiqué.", ticket.id))
.colour(Colour::from_rgb(0, 200, 120))
.timestamp(Utc::now()),
)
.await;
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_cleanup(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_cleanup(ctx, msg, args).await;
}
pub struct CleanupCommand;
pub static COMMAND_DESCRIPTOR: CleanupCommand = CleanupCommand;
impl crate::commands::command_contract::CommandSpec for CleanupCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "cleanup",
command: "cleanup",
category: "admin",
params: "<salon_vocal>",
summary: "Vide un salon vocal",
description: "Deconnecte tous les utilisateurs presents dans un salon vocal cible.",
examples: &["+cleanup #General"],
alias_source_key: "cleanup",
default_aliases: &["vclean", "vcleanup"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::moderation_tools;
pub async fn handle_clear_all_sanctions(ctx: &Context, msg: &Message) {
moderation_tools::handle_clear_all_sanctions(ctx, msg).await;
}
pub struct ClearAllSanctionsCommand;
pub static COMMAND_DESCRIPTOR: ClearAllSanctionsCommand = ClearAllSanctionsCommand;
impl crate::commands::command_contract::CommandSpec for ClearAllSanctionsCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "clear_all_sanctions",
command: "clear all sanctions",
category: "admin",
params: "aucun",
summary: "Supprime toutes les sanctions du serveur",
description: "Efface toutes les sanctions de tous les membres du serveur.",
examples: &["+clear all sanctions"],
alias_source_key: "clear_all_sanctions",
default_aliases: &["casanctions"],
default_permission: 8,
}
}
}
+54
View File
@@ -0,0 +1,54 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::admin_common::ensure_owner;
use crate::commands::common::send_embed;
use crate::db::{DbPoolKey, clear_blacklist};
pub async fn handle_clear_bl(ctx: &Context, msg: &Message) {
if ensure_owner(ctx, msg).await.is_err() {
return;
}
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 {
let embed = serenity::builder::CreateEmbed::new()
.title("Erreur")
.description("DB indisponible.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let count = clear_blacklist(&pool, bot_id).await.unwrap_or(0);
let embed = serenity::builder::CreateEmbed::new()
.title("Blacklist réinitialisée")
.description(format!("{} membre(s) retiré(s) de la blacklist.", count))
.color(0x57F287);
send_embed(ctx, msg, embed).await;
}
pub struct ClearBlCommand;
pub static COMMAND_DESCRIPTOR: ClearBlCommand = ClearBlCommand;
impl crate::commands::command_contract::CommandSpec for ClearBlCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "clear_bl",
command: "clear bl",
category: "admin",
params: "aucun",
summary: "Vide la blacklist globale",
description: "Supprime toutes les entrees de la blacklist globale.",
examples: &["+clear bl", "+cl", "+help clear bl"],
alias_source_key: "clear_bl",
default_aliases: &["cbl"],
default_permission: 9,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::moderation_tools;
pub async fn handle_clear_messages(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_clear_messages(ctx, msg, args).await;
}
pub struct ClearMessagesCommand;
pub static COMMAND_DESCRIPTOR: ClearMessagesCommand = ClearMessagesCommand;
impl crate::commands::command_contract::CommandSpec for ClearMessagesCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "clear_messages",
command: "clear",
category: "admin",
params: "<nombre> [@membre/ID]",
summary: "Supprime des messages dans le salon",
description: "Supprime un nombre de messages, optionnellement filtres par membre.",
examples: &["+clear 20", "+clear 20 @User"],
alias_source_key: "clear_messages",
default_aliases: &["purge"],
default_permission: 8,
}
}
}
+54
View File
@@ -0,0 +1,54 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::admin_common::ensure_owner;
use crate::commands::common::send_embed;
use crate::db::{DbPoolKey, clear_bot_owners};
pub async fn handle_clear_owners(ctx: &Context, msg: &Message) {
if ensure_owner(ctx, msg).await.is_err() {
return;
}
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 {
let embed = serenity::builder::CreateEmbed::new()
.title("Erreur")
.description("DB indisponible.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let count = clear_bot_owners(&pool, bot_id).await.unwrap_or(0);
let embed = serenity::builder::CreateEmbed::new()
.title("Owners réinitialisés")
.description(format!("{} owner(s) supprimé(s).", count))
.color(0x57F287);
send_embed(ctx, msg, embed).await;
}
pub struct ClearOwnersCommand;
pub static COMMAND_DESCRIPTOR: ClearOwnersCommand = ClearOwnersCommand;
impl crate::commands::command_contract::CommandSpec for ClearOwnersCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "clear_owners",
command: "clear owners",
category: "admin",
params: "aucun",
summary: "Vide la liste des owners",
description: "Supprime tous les owners supplementaires en base de donnees.",
examples: &["+clear owners", "+cs", "+help clear owners"],
alias_source_key: "clear_owners",
default_aliases: &["cro"],
default_permission: 9,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::moderation_tools;
pub async fn handle_clear_sanctions(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_clear_sanctions(ctx, msg, args).await;
}
pub struct ClearSanctionsCommand;
pub static COMMAND_DESCRIPTOR: ClearSanctionsCommand = ClearSanctionsCommand;
impl crate::commands::command_contract::CommandSpec for ClearSanctionsCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "clear_sanctions",
command: "clear sanctions",
category: "admin",
params: "<@membre/ID>",
summary: "Supprime toutes les sanctions d un membre",
description: "Efface completement les sanctions d un membre cible.",
examples: &["+clear sanctions @User"],
alias_source_key: "clear_sanctions",
default_aliases: &["csanctions"],
default_permission: 8,
}
}
}
+76
View File
@@ -0,0 +1,76 @@
use chrono::Utc;
use serenity::builder::CreateEmbed;
use serenity::model::Colour;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::common::send_embed;
use crate::db;
pub async fn handle_close(ctx: &Context, msg: &Message, args: &[&str]) {
let Some(guild_id) = msg.guild_id else {
return;
};
let reason = if args.is_empty() {
None
} else {
Some(args.join(" "))
};
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 = msg.channel_id.get() as i64;
let Some(ticket) = db::get_ticket_by_channel(&pool, bot_id, guild_id_i64, channel_id)
.await
.ok()
.flatten()
else {
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Erreur")
.description("Ce salon n'est pas reconnu comme un ticket.")
.color(0xED4245),
)
.await;
return;
};
if db::close_ticket(&pool, ticket.id, reason.clone())
.await
.is_err()
{
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Erreur")
.description("Impossible de fermer ce ticket.")
.color(0xED4245),
)
.await;
return;
}
let mut embed = CreateEmbed::new()
.title("Ticket fermé")
.description(format!("Le ticket #{} a été fermé.", ticket.id))
.colour(Colour::from_rgb(255, 120, 0))
.timestamp(Utc::now());
if let Some(reason) = reason {
embed = embed.field("Raison", reason, false);
}
send_embed(ctx, msg, embed).await;
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_cmute(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_cmute(ctx, msg, args, false).await;
}
pub struct CmuteCommand;
pub static COMMAND_DESCRIPTOR: CmuteCommand = CmuteCommand;
impl crate::commands::command_contract::CommandSpec for CmuteCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "cmute",
command: "cmute",
category: "admin",
params: "<@membre/ID[,..]> [raison]",
summary: "Mute salon",
description: "Mute un membre sur le salon courant.",
examples: &["+cmute @User"],
alias_source_key: "cmute",
default_aliases: &["cm"],
default_permission: 8,
}
}
}
+31
View File
@@ -0,0 +1,31 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_create(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_create(ctx, msg, args).await;
}
pub struct CreateCommand;
pub static COMMAND_DESCRIPTOR: CreateCommand = CreateCommand;
impl crate::commands::command_contract::CommandSpec for CreateCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "create",
command: "create",
category: "admin",
params: "[emoji/url] [nom]",
summary: "Cree un emoji custom",
description: "Cree un emoji custom a partir d'une image, d'un lien ou d'un emoji nitro.",
examples: &[
"+create <:blob:123456789012345678> blobcopy",
"+create https://... logo",
],
alias_source_key: "create",
default_aliases: &["mkemoji", "ce"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::moderation_tools;
pub async fn handle_del_sanction(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_del_sanction(ctx, msg, args).await;
}
pub struct DelSanctionCommand;
pub static COMMAND_DESCRIPTOR: DelSanctionCommand = DelSanctionCommand;
impl crate::commands::command_contract::CommandSpec for DelSanctionCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "del_sanction",
command: "del sanction",
category: "admin",
params: "<@membre/ID> <nombre>",
summary: "Supprime une sanction d un membre",
description: "Supprime une sanction specifique dans l historique d un membre.",
examples: &["+del sanction @User 1"],
alias_source_key: "del_sanction",
default_aliases: &["delsanction"],
default_permission: 8,
}
}
}
+27
View File
@@ -0,0 +1,27 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_delrole(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_add_del_role(ctx, msg, args, false).await;
}
pub struct DelroleCommand;
pub static COMMAND_DESCRIPTOR: DelroleCommand = DelroleCommand;
impl crate::commands::command_contract::CommandSpec for DelroleCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "delrole",
command: "delrole",
category: "admin",
params: "<@membre/ID[,..]> <@role/ID>",
summary: "Retire un role",
description: "Retire un role a un ou plusieurs membres.",
examples: &["+delrole @User @Membre"],
alias_source_key: "delrole",
default_aliases: &["dr"],
default_permission: 8,
}
}
}
+27
View File
@@ -0,0 +1,27 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_derank(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_derank(ctx, msg, args).await;
}
pub struct DerankCommand;
pub static COMMAND_DESCRIPTOR: DerankCommand = DerankCommand;
impl crate::commands::command_contract::CommandSpec for DerankCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "derank",
command: "derank",
category: "admin",
params: "<@membre/ID[,..]>",
summary: "Retire tous les roles",
description: "Retire tous les roles gerables d un membre.",
examples: &["+derank @User"],
alias_source_key: "derank",
default_aliases: &["drk"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_embed(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_embed_builder(ctx, msg, args).await;
}
pub struct EmbedCommand;
pub static COMMAND_DESCRIPTOR: EmbedCommand = EmbedCommand;
impl crate::commands::command_contract::CommandSpec for EmbedCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "embed",
command: "embed",
category: "admin",
params: "title | description (v1)",
summary: "Ouvre le generateur d'embed",
description: "Affiche un generateur d'embed interactif version rapide.",
examples: &["+embed"],
alias_source_key: "embed",
default_aliases: &["emb"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_end(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_end(ctx, msg, args).await;
}
pub struct EndCommand;
pub static COMMAND_DESCRIPTOR: EndCommand = EndCommand;
impl crate::commands::command_contract::CommandSpec for EndCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "end",
command: "end",
category: "admin",
params: "giveaway <id_message>",
summary: "Termine un giveaway par ID",
description: "Permet de stopper instantanement un giveaway avec l'identifiant du message.",
examples: &["+end giveaway 123456789012345678"],
alias_source_key: "end",
default_aliases: &["gend"],
default_permission: 0,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_giveaway(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_giveaway(ctx, msg, args).await;
}
pub struct GiveawayCommand;
pub static COMMAND_DESCRIPTOR: GiveawayCommand = GiveawayCommand;
impl crate::commands::command_contract::CommandSpec for GiveawayCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "giveaway",
command: "giveaway",
category: "admin",
params: "aucun",
summary: "Ouvre un menu de creation de giveaway",
description: "Affiche une interface rapide pour initier un giveaway depuis le salon courant.",
examples: &["+giveaway"],
alias_source_key: "giveaway",
default_aliases: &["gstart", "gw"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_hide(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_hide_unhide(ctx, msg, args, true).await;
}
pub struct HideCommand;
pub static COMMAND_DESCRIPTOR: HideCommand = HideCommand;
impl crate::commands::command_contract::CommandSpec for HideCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "hide",
command: "hide",
category: "admin",
params: "[salon]",
summary: "Cache un salon",
description: "Retire la visibilite d un salon.",
examples: &["+hide", "+hide #general"],
alias_source_key: "hide",
default_aliases: &["hd"],
default_permission: 8,
}
}
}
+27
View File
@@ -0,0 +1,27 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_hideall(ctx: &Context, msg: &Message) {
moderation_tools::handle_hideall_unhideall(ctx, msg, true).await;
}
pub struct HideallCommand;
pub static COMMAND_DESCRIPTOR: HideallCommand = HideallCommand;
impl crate::commands::command_contract::CommandSpec for HideallCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "hideall",
command: "hideall",
category: "admin",
params: "aucun",
summary: "Cache tous les salons",
description: "Retire la visibilite de tous les salons.",
examples: &["+hideall"],
alias_source_key: "hideall",
default_aliases: &["hda"],
default_permission: 8,
}
}
}
+85
View File
@@ -0,0 +1,85 @@
use serenity::builder::CreateEmbed;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::common::send_embed;
use crate::commands::server::resolve_guild_target;
pub async fn handle_invite(ctx: &Context, msg: &Message, args: &[&str]) {
if args.is_empty() {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Usage: `+invite <ID/nombre>`")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
}
let Some(guild_id) = resolve_guild_target(ctx, args[0]).await else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Serveur introuvable.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let Ok(channels) = guild_id.channels(&ctx.http).await else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Impossible de lire les salons.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let mut invite_url = None;
for channel in channels.values() {
if matches!(channel.kind, ChannelType::Text | ChannelType::News) {
if let Ok(invite) = channel
.create_invite(
&ctx.http,
serenity::builder::CreateInvite::new().max_age(3600),
)
.await
{
invite_url = Some(invite.url());
break;
}
}
}
let Some(url) = invite_url else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Aucun salon éligible pour créer une invitation.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let embed = CreateEmbed::new()
.title("Invitation créée")
.description(url)
.color(0x57F287);
send_embed(ctx, msg, embed).await;
}
pub struct InviteCommand;
pub static COMMAND_DESCRIPTOR: InviteCommand = InviteCommand;
impl crate::commands::command_contract::CommandSpec for InviteCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "invite",
command: "invite",
category: "admin",
params: "<ID_serveur/index>",
summary: "Cree une invitation serveur",
description: "Cree une invitation temporaire sur un serveur cible accessible par le bot.",
examples: &["+invite", "+ie", "+help invite"],
alias_source_key: "invite",
default_aliases: &["ivt"],
default_permission: 8,
}
}
}
+30
View File
@@ -0,0 +1,30 @@
use crate::commands::logs_service;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_join(ctx: &Context, msg: &Message, args: &[&str]) {
logs_service::handle_join_leave_settings(ctx, msg, args, "join").await;
}
pub struct JoinCommand;
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 {
key: "join",
command: "join",
category: "admin",
params: "settings [on/off] [salon] [message]",
summary: "Parametre les actions de join",
description: "Permet de configurer les actions quand un membre rejoint.",
examples: &[
"+join settings",
"+join settings on #welcome Bienvenue {user}",
],
alias_source_key: "join",
default_aliases: &["jset"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_kick(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_kick(ctx, msg, args).await;
}
pub struct KickCommand;
pub static COMMAND_DESCRIPTOR: KickCommand = KickCommand;
impl crate::commands::command_contract::CommandSpec for KickCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "kick",
command: "kick",
category: "admin",
params: "<@membre/ID[,..]> [raison]",
summary: "Expulse un membre",
description: "Kick un ou plusieurs membres.",
examples: &["+kick @User"],
alias_source_key: "kick",
default_aliases: &["k"],
default_permission: 8,
}
}
}
+49
View File
@@ -0,0 +1,49 @@
use serenity::builder::CreateEmbed;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::common::send_embed;
use crate::commands::server::resolve_guild_target;
pub async fn handle_leave(ctx: &Context, msg: &Message, args: &[&str]) {
let target = if args.is_empty() {
msg.guild_id
} else {
resolve_guild_target(ctx, args[0]).await
};
let Some(guild_id) = target else {
let embed = CreateEmbed::new()
.title("Erreur")
.description("Serveur introuvable.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let _ = guild_id.leave(&ctx.http).await;
let embed = CreateEmbed::new()
.title("Serveur quitté")
.description(format!("Le bot a quitté `{}`.", guild_id.get()))
.color(0x57F287);
send_embed(ctx, msg, embed).await;
}
pub struct LeaveCommand;
pub static COMMAND_DESCRIPTOR: LeaveCommand = LeaveCommand;
impl crate::commands::command_contract::CommandSpec for LeaveCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "leave",
command: "leave",
category: "admin",
params: "[ID_serveur/index]",
summary: "Fait quitter un serveur",
description: "Force le bot a quitter un serveur cible ou le serveur courant.",
examples: &["+leave", "+le", "+help leave"],
alias_source_key: "leave",
default_aliases: &["lvg"],
default_permission: 9,
}
}
}
+31
View File
@@ -0,0 +1,31 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::logs_service;
pub async fn handle_leave_settings(ctx: &Context, msg: &Message, args: &[&str]) {
logs_service::handle_join_leave_settings(ctx, msg, args, "leave").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 {
key: "leave_settings",
command: "leave settings",
category: "admin",
params: "settings [on/off] [salon] [message]",
summary: "Parametre les actions de leave",
description: "Configure les actions a executer quand un membre quitte le serveur.",
examples: &[
"+leave settings",
"+leave settings on #logs {user} a quitte",
],
alias_source_key: "leave_settings",
default_aliases: &["lset"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_lock(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_lock_unlock(ctx, msg, args, true).await;
}
pub struct LockCommand;
pub static COMMAND_DESCRIPTOR: LockCommand = LockCommand;
impl crate::commands::command_contract::CommandSpec for LockCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "lock",
command: "lock",
category: "admin",
params: "[salon]",
summary: "Ferme un salon",
description: "Verrouille un salon texte ou vocal.",
examples: &["+lock", "+lock #general"],
alias_source_key: "lock",
default_aliases: &["lk"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_lockall(ctx: &Context, msg: &Message) {
moderation_tools::handle_lockall_unlockall(ctx, msg, true).await;
}
pub struct LockallCommand;
pub static COMMAND_DESCRIPTOR: LockallCommand = LockallCommand;
impl crate::commands::command_contract::CommandSpec for LockallCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "lockall",
command: "lockall",
category: "admin",
params: "aucun",
summary: "Ferme tous les salons",
description: "Verrouille tous les salons du serveur.",
examples: &["+lockall"],
alias_source_key: "lockall",
default_aliases: &["lka"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_massiverole(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_massive_role(ctx, msg, args, true).await;
}
pub struct MassiveRoleCommand;
pub static COMMAND_DESCRIPTOR: MassiveRoleCommand = MassiveRoleCommand;
impl crate::commands::command_contract::CommandSpec for MassiveRoleCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "massiverole",
command: "massiverole",
category: "admin",
params: "<role_cible> [role_filtre]",
summary: "Ajoute un role en masse",
description: "Ajoute un role a tous les membres ou a ceux qui ont deja un role filtre.",
examples: &["+massiverole @VIP", "+massiverole @VIP @Membres"],
alias_source_key: "massiverole",
default_aliases: &["mrole", "mr"],
default_permission: 8,
}
}
}
+27
View File
@@ -0,0 +1,27 @@
use crate::commands::logs_service;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_messagelog(ctx: &Context, msg: &Message, args: &[&str]) {
logs_service::handle_log_toggle(ctx, msg, args, "message", "MessageLog").await;
}
pub struct MessagelogCommand;
pub static COMMAND_DESCRIPTOR: MessagelogCommand = MessagelogCommand;
impl crate::commands::command_contract::CommandSpec for MessagelogCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "messagelog",
command: "messagelog",
category: "admin",
params: "<on [salon]|off>",
summary: "Active les logs de messages",
description: "Active ou desactive les logs des messages supprimes et edites.",
examples: &["+messagelog on #logs", "+messagelog off"],
alias_source_key: "messagelog",
default_aliases: &["msglog"],
default_permission: 8,
}
}
}
+27
View File
@@ -0,0 +1,27 @@
use crate::commands::logs_service;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_modlog(ctx: &Context, msg: &Message, args: &[&str]) {
logs_service::handle_log_toggle(ctx, msg, args, "moderation", "ModLog").await;
}
pub struct ModlogCommand;
pub static COMMAND_DESCRIPTOR: ModlogCommand = ModlogCommand;
impl crate::commands::command_contract::CommandSpec for ModlogCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "modlog",
command: "modlog",
category: "admin",
params: "<on [salon]|off>",
summary: "Active les logs de moderation",
description: "Active ou desactive les logs de moderation dans un salon cible.",
examples: &["+modlog on #logs", "+modlog off"],
alias_source_key: "modlog",
default_aliases: &["mlog"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_mute(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_mute(ctx, msg, args, false).await;
}
pub struct MuteCommand;
pub static COMMAND_DESCRIPTOR: MuteCommand = MuteCommand;
impl crate::commands::command_contract::CommandSpec for MuteCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "mute",
command: "mute",
category: "admin",
params: "<@membre/ID[,..]> [raison]",
summary: "Mute un membre",
description: "Applique un mute a un ou plusieurs membres.",
examples: &["+mute @User abus"],
alias_source_key: "mute",
default_aliases: &["tmute"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_mutelist(ctx: &Context, msg: &Message) {
moderation_tools::handle_mutelist(ctx, msg).await;
}
pub struct MutelistCommand;
pub static COMMAND_DESCRIPTOR: MutelistCommand = MutelistCommand;
impl crate::commands::command_contract::CommandSpec for MutelistCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "mutelist",
command: "mutelist",
category: "admin",
params: "aucun",
summary: "Liste les mutes",
description: "Affiche tous les mutes en cours.",
examples: &["+mutelist"],
alias_source_key: "mutelist",
default_aliases: &["ml"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_newsticker(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_new_sticker(ctx, msg, args).await;
}
pub struct NewStickerCommand;
pub static COMMAND_DESCRIPTOR: NewStickerCommand = NewStickerCommand;
impl crate::commands::command_contract::CommandSpec for NewStickerCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "newsticker",
command: "newsticker",
category: "admin",
params: "[nom]",
summary: "Cree un sticker serveur",
description: "Cree un nouveau sticker a partir d'un sticker ou fichier repondu.",
examples: &["+newsticker cool_pack"],
alias_source_key: "newsticker",
default_aliases: &["stcreate", "nst"],
default_permission: 8,
}
}
}
+27
View File
@@ -0,0 +1,27 @@
use crate::commands::logs_service;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_nolog(ctx: &Context, msg: &Message, args: &[&str]) {
logs_service::handle_nolog(ctx, msg, args).await;
}
pub struct NologCommand;
pub static COMMAND_DESCRIPTOR: NologCommand = NologCommand;
impl crate::commands::command_contract::CommandSpec for NologCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "nolog",
command: "nolog",
category: "admin",
params: "<add/del> [salon] [message|voice|all]",
summary: "Exclut des salons des logs",
description: "Desactive ou reactive les logs message/voice pour certains salons.",
examples: &["+nolog add #secret all", "+nolog del #secret message"],
alias_source_key: "nolog",
default_aliases: &["nlg"],
default_permission: 8,
}
}
}
+60
View File
@@ -0,0 +1,60 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::admin_common::{app_owner_id, ensure_owner};
use crate::commands::common::{add_list_fields, send_embed, theme_color};
use crate::db::{DbPoolKey, list_bot_owners};
pub async fn handle_owner(ctx: &Context, msg: &Message, _args: &[&str]) {
if ensure_owner(ctx, msg).await.is_err() {
return;
}
let bot_id = ctx.cache.current_user().id;
let pool = {
let data = ctx.data.read().await;
data.get::<DbPoolKey>().cloned()
};
let mut lines: Vec<String> = Vec::new();
if let Some(app_owner) = app_owner_id(ctx).await {
lines.push(format!("<@{}> (owner application)", app_owner.get()));
}
if let Some(pool) = pool {
if let Ok(extra) = list_bot_owners(&pool, bot_id).await {
for uid in extra {
lines.push(format!("<@{}>", uid));
}
}
}
let color = theme_color(ctx).await;
let mut embed = serenity::builder::CreateEmbed::new()
.title("Owners du bot")
.color(color);
embed = add_list_fields(embed, &lines, "Owners");
send_embed(ctx, msg, embed).await;
}
pub struct OwnerCommand;
pub static COMMAND_DESCRIPTOR: OwnerCommand = OwnerCommand;
impl crate::commands::command_contract::CommandSpec for OwnerCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "owner",
command: "owner",
category: "admin",
params: "aucun",
summary: "Liste les owners du bot",
description: "Affiche l owner application et les owners ajoutes en base.",
examples: &["+owner", "+or", "+help owner"],
alias_source_key: "owner",
default_aliases: &["own"],
default_permission: 9,
}
}
}
+27
View File
@@ -0,0 +1,27 @@
use crate::commands::logs_service;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_raidlog(ctx: &Context, msg: &Message, args: &[&str]) {
logs_service::handle_raidlog(ctx, msg, args).await;
}
pub struct RaidlogCommand;
pub static COMMAND_DESCRIPTOR: RaidlogCommand = RaidlogCommand;
impl crate::commands::command_contract::CommandSpec for RaidlogCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "raidlog",
command: "raidlog",
category: "admin",
params: "[salon]|off",
summary: "Active les logs antiraid",
description: "Active les logs antiraid dans un salon ou les desactive.",
examples: &["+raidlog #logs", "+raidlog off"],
alias_source_key: "raidlog",
default_aliases: &["rdlog"],
default_permission: 8,
}
}
}
+121
View File
@@ -0,0 +1,121 @@
use chrono::Utc;
use serenity::builder::CreateEmbed;
use serenity::model::Colour;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::common::send_embed;
use crate::db;
fn sanitize_ticket_name(input: &str) -> String {
let mut out = String::new();
let mut previous_dash = false;
for ch in input.to_lowercase().chars() {
let keep = ch.is_ascii_alphanumeric();
let dash = ch.is_whitespace() || ch == '-' || ch == '_';
if keep {
out.push(ch);
previous_dash = false;
} else if dash && !previous_dash {
out.push('-');
previous_dash = true;
}
}
out.trim_matches('-').to_string()
}
pub async fn handle_rename(ctx: &Context, msg: &Message, args: &[&str]) {
let Some(guild_id) = msg.guild_id else {
return;
};
let new_name_raw = args.join(" ").trim().to_string();
if new_name_raw.is_empty() {
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Erreur")
.description("Utilisation: +rename <nom>")
.color(0xED4245),
)
.await;
return;
}
let new_name = sanitize_ticket_name(&new_name_raw);
if new_name.is_empty() {
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Erreur")
.description("Nom invalide.")
.color(0xED4245),
)
.await;
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 = msg.channel_id.get() as i64;
let Some(ticket) = db::get_ticket_by_channel(&pool, bot_id, guild_id_i64, channel_id)
.await
.ok()
.flatten()
else {
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Erreur")
.description("Ce salon n'est pas reconnu comme un ticket.")
.color(0xED4245),
)
.await;
return;
};
if msg
.channel_id
.edit(&ctx.http, serenity::builder::EditChannel::new().name(new_name.clone()))
.await
.is_err()
{
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Erreur")
.description("Impossible de renommer le salon.")
.color(0xED4245),
)
.await;
return;
}
let _ = db::update_ticket_title(&pool, ticket.id, &new_name).await;
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Ticket renommé")
.description(format!("Nouveau nom: `{}`", new_name))
.colour(Colour::from_rgb(0, 200, 120))
.timestamp(Utc::now()),
)
.await;
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_renew(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_renew(ctx, msg, args).await;
}
pub struct RenewCommand;
pub static COMMAND_DESCRIPTOR: RenewCommand = RenewCommand;
impl crate::commands::command_contract::CommandSpec for RenewCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "renew",
command: "renew",
category: "admin",
params: "[salon]",
summary: "Recree un salon textuel",
description: "Supprime puis recree un salon textuel en conservant les options principales.",
examples: &["+renew", "+renew #general"],
alias_source_key: "renew",
default_aliases: &["nuke", "rebuildch"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_reroll(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_reroll(ctx, msg, args).await;
}
pub struct RerollCommand;
pub static COMMAND_DESCRIPTOR: RerollCommand = RerollCommand;
impl crate::commands::command_contract::CommandSpec for RerollCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "reroll",
command: "reroll",
category: "admin",
params: "aucun (en reponse a un message)",
summary: "Relance un tirage giveaway",
description: "Choisit un nouveau gagnant depuis le message cible.",
examples: &["+reroll"],
alias_source_key: "reroll",
default_aliases: &["rro", "greroll"],
default_permission: 8,
}
}
}
+27
View File
@@ -0,0 +1,27 @@
use crate::commands::logs_service;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_rolelog(ctx: &Context, msg: &Message, args: &[&str]) {
logs_service::handle_log_toggle(ctx, msg, args, "role", "RoleLog").await;
}
pub struct RolelogCommand;
pub static COMMAND_DESCRIPTOR: RolelogCommand = RolelogCommand;
impl crate::commands::command_contract::CommandSpec for RolelogCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "rolelog",
command: "rolelog",
category: "admin",
params: "<on [salon]|off>",
summary: "Active les logs de roles",
description: "Active ou desactive les logs des roles.",
examples: &["+rolelog on #logs", "+rolelog off"],
alias_source_key: "rolelog",
default_aliases: &["rlog"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::moderation_tools;
pub async fn handle_sanctions(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_sanctions(ctx, msg, args).await;
}
pub struct SanctionsCommand;
pub static COMMAND_DESCRIPTOR: SanctionsCommand = SanctionsCommand;
impl crate::commands::command_contract::CommandSpec for SanctionsCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "sanctions",
command: "sanctions",
category: "admin",
params: "<@membre/ID>",
summary: "Affiche les sanctions d un membre",
description: "Liste l historique des sanctions d un membre.",
examples: &["+sanctions @User"],
alias_source_key: "sanctions",
default_aliases: &["sanct"],
default_permission: 8,
}
}
}
+42
View File
@@ -0,0 +1,42 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::admin_common::ensure_owner;
pub async fn handle_say(ctx: &Context, msg: &Message, args: &[&str]) {
if ensure_owner(ctx, msg).await.is_err() {
return;
}
if args.is_empty() {
let embed = serenity::builder::CreateEmbed::new()
.title("Erreur")
.description("Usage: `+say <message>`")
.color(0xED4245);
crate::commands::common::send_embed(ctx, msg, embed).await;
return;
}
let text = args.join(" ");
let _ = msg.channel_id.say(&ctx.http, text).await;
}
pub struct SayCommand;
pub static COMMAND_DESCRIPTOR: SayCommand = SayCommand;
impl crate::commands::command_contract::CommandSpec for SayCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "say",
command: "say",
category: "admin",
params: "<message...>",
summary: "Fait parler le bot",
description: "Envoie un message brut dans le salon courant via le bot.",
examples: &["+say", "+sy", "+help say"],
alias_source_key: "say",
default_aliases: &["sym"],
default_permission: 8,
}
}
}
+31
View File
@@ -0,0 +1,31 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::logs_service;
pub async fn handle_set_boostembed(ctx: &Context, msg: &Message, args: &[&str]) {
logs_service::handle_set_boostembed(ctx, msg, args).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 {
key: "set_boostembed",
command: "set boostembed",
category: "admin",
params: "<title|description|color> <valeur>",
summary: "Parametre l embed de boost",
description: "Configure le titre, la description et la couleur de l embed boost.",
examples: &[
"+set boostembed title Merci",
"+set boostembed color #FF66CC",
],
alias_source_key: "set_boostembed",
default_aliases: &["sboostembed"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::logs_service;
pub async fn handle_set_modlogs(ctx: &Context, msg: &Message, args: &[&str]) {
logs_service::handle_set_modlogs(ctx, msg, args).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 {
key: "set_modlogs",
command: "set modlogs",
category: "admin",
params: "[event on/off]",
summary: "Parametre les evenements de modlogs",
description: "Affiche ou modifie les evenements qui apparaissent dans les logs de moderation.",
examples: &["+set modlogs", "+set modlogs warn off"],
alias_source_key: "set_modlogs",
default_aliases: &["smodlog"],
default_permission: 8,
}
}
}
+472
View File
@@ -0,0 +1,472 @@
use chrono::Utc;
use serenity::builder::{
CreateActionRow, CreateButton, CreateEmbed, CreateInputText, CreateInteractionResponse,
CreateInteractionResponseMessage, CreateMessage, CreateModal,
};
use serenity::model::application::{
ActionRowComponent, ButtonStyle, ComponentInteraction, InputTextStyle, ModalInteraction,
};
use serenity::model::Colour;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::common::send_embed;
use crate::db;
const SUGGESTION_MENU: &str = "suggestion:settings";
fn parse_owner_id(custom_id: &str) -> Option<(String, u64)> {
let mut parts = custom_id.rsplitn(2, ':');
let owner = parts.next()?.parse::<u64>().ok()?;
let action = parts.next()?.to_string();
Some((action, owner))
}
fn modal_value(modal: &ModalInteraction, wanted_id: &str) -> Option<String> {
for row in &modal.data.components {
for component in &row.components {
if let ActionRowComponent::InputText(input) = component {
if input.custom_id == wanted_id {
return input.value.clone();
}
}
}
}
None
}
fn suggestion_embed(author: &User, content: &str) -> CreateEmbed {
CreateEmbed::new()
.title("💡 Suggestion")
.description(content)
.colour(Colour::from_rgb(255, 200, 0))
.author(
serenity::builder::CreateEmbedAuthor::new(&author.name).icon_url(author.face()),
)
.timestamp(Utc::now())
}
fn suggestion_settings_embed(settings: &db::SuggestionSettings) -> CreateEmbed {
let mut embed = CreateEmbed::new()
.title("Gestion des suggestions")
.description("Configure le système de suggestions du serveur.")
.colour(Colour::from_rgb(255, 200, 0))
.timestamp(Utc::now())
.field("Statut", if settings.enabled { "Actif" } else { "Inactif" }, true);
if let Some(channel_id) = settings.channel_id {
embed = embed.field("Canal", format!("<#{}>", channel_id), true);
}
if let Some(approve_channel_id) = settings.approve_channel_id {
embed = embed.field("Validation", format!("<#{}>", approve_channel_id), true);
}
embed
}
fn suggestion_components(owner_id: UserId, settings: &db::SuggestionSettings) -> Vec<CreateActionRow> {
let toggle_label = if settings.enabled { "Désactiver" } else { "Activer" };
vec![CreateActionRow::Buttons(vec![
CreateButton::new(format!("{}:submit:{}", SUGGESTION_MENU, owner_id.get()))
.label("Soumettre")
.style(ButtonStyle::Success),
CreateButton::new(format!("{}:configure:{}", SUGGESTION_MENU, owner_id.get()))
.label("Configurer")
.style(ButtonStyle::Secondary),
CreateButton::new(format!("{}:toggle:{}", SUGGESTION_MENU, owner_id.get()))
.label(toggle_label)
.style(ButtonStyle::Primary),
CreateButton::new(format!("{}:refresh:{}", SUGGESTION_MENU, owner_id.get()))
.label("Rafraîchir")
.style(ButtonStyle::Secondary),
])]
}
async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
let data = ctx.data.read().await;
data.get::<db::DbPoolKey>().cloned()
}
async fn show_menu(ctx: &Context, msg: &Message) {
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 settings = db::get_or_create_suggestion_settings(&pool, bot_id, guild_id.get() as i64)
.await
.ok();
let Some(settings) = settings else {
return;
};
let _ = msg
.channel_id
.send_message(
&ctx.http,
CreateMessage::new()
.embed(suggestion_settings_embed(&settings))
.components(suggestion_components(msg.author.id, &settings)),
)
.await;
}
async fn submit_suggestion(
ctx: &Context,
guild_id: GuildId,
author: &User,
content: String,
) -> Result<(), String> {
let pool = pool(ctx).await.ok_or_else(|| "Base de données indisponible".to_string())?;
let bot_id = ctx.cache.current_user().id.get() as i64;
let settings = db::get_or_create_suggestion_settings(&pool, bot_id, guild_id.get() as i64)
.await
.map_err(|e| format!("Erreur: {e}"))?;
if !settings.enabled {
return Err("Le système de suggestions est désactivé.".to_string());
}
let channel_id = settings.channel_id.ok_or_else(|| "Canal de suggestions non configuré".to_string())?;
let channel = ChannelId::new(channel_id as u64)
.to_channel(&ctx.http)
.await
.map_err(|e| format!("Erreur: {e}"))?;
let guild_channel = channel.guild().ok_or_else(|| "Canal de suggestions introuvable".to_string())?;
let message = guild_channel
.send_message(
&ctx.http,
serenity::builder::CreateMessage::new().embed(suggestion_embed(author, &content)),
)
.await
.map_err(|e| format!("Erreur: {e}"))?;
db::create_suggestion(
&pool,
bot_id,
guild_id.get() as i64,
channel_id,
message.id.get() as i64,
author.id.get() as i64,
content.clone(),
)
.await
.map_err(|e| format!("Erreur: {e}"))?;
let _ = message.react(&ctx.http, '👍').await;
let _ = message.react(&ctx.http, '👎').await;
if let Ok(channels) = db::get_autopublish_channels(&pool, bot_id, guild_id.get() as i64).await {
for autopublish_channel in channels {
if autopublish_channel.channel_id == channel_id {
continue;
}
let _ = ChannelId::new(autopublish_channel.channel_id as u64)
.send_message(
&ctx.http,
serenity::builder::CreateMessage::new()
.embed(suggestion_embed(author, &content)),
)
.await;
}
}
Ok(())
}
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")
.color(0xED4245),
)
.await;
return;
}
let Some(guild_id) = msg.guild_id else {
return;
};
let content = args.join(" ");
if content.trim().is_empty() {
return;
}
if let Err(error) = submit_suggestion(ctx, guild_id, &msg.author, content).await {
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Suggestion")
.description(error)
.color(0xED4245),
)
.await;
return;
}
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Suggestion envoyée")
.description("La suggestion a été publiée.")
.colour(Colour::from_rgb(0, 200, 120))
.timestamp(Utc::now()),
)
.await;
}
pub async fn handle_component_interaction(ctx: &Context, component: &ComponentInteraction) -> bool {
if !component.data.custom_id.starts_with(SUGGESTION_MENU) {
return false;
}
let Some((action, owner_id)) = parse_owner_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 du menu peut l'utiliser.")
.ephemeral(true),
),
)
.await;
return true;
}
let Some(guild_id) = component.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 = db::get_or_create_suggestion_settings(&pool, bot_id, guild_id.get() as i64)
.await
.ok();
let Some(settings) = settings else {
return true;
};
if action.ends_with(":refresh") {
let _ = component
.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.embed(suggestion_settings_embed(&settings))
.components(suggestion_components(component.user.id, &settings)),
),
)
.await;
return true;
}
if action.ends_with(":toggle") {
if let Ok(updated) = db::update_suggestion_settings(
&pool,
bot_id,
guild_id.get() as i64,
!settings.enabled,
settings.channel_id,
settings.approve_channel_id,
)
.await
{
let _ = component
.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.embed(suggestion_settings_embed(&updated))
.components(suggestion_components(component.user.id, &updated)),
),
)
.await;
}
return true;
}
if action.ends_with(":configure") {
let modal = CreateModal::new(component.data.custom_id.clone(), "Configurer les suggestions")
.components(vec![
CreateActionRow::InputText(
CreateInputText::new(InputTextStyle::Short, "Canal des suggestions", "channel_id")
.required(false),
),
CreateActionRow::InputText(
CreateInputText::new(InputTextStyle::Short, "Canal d'approbation", "approve_channel_id")
.required(false),
),
]);
let _ = component
.create_response(&ctx.http, CreateInteractionResponse::Modal(modal))
.await;
return true;
}
if action.ends_with(":submit") {
let modal = CreateModal::new(component.data.custom_id.clone(), "Soumettre une suggestion")
.components(vec![CreateActionRow::InputText(
CreateInputText::new(InputTextStyle::Paragraph, "Contenu", "content")
.required(true)
.max_length(2000),
)]);
let _ = component
.create_response(&ctx.http, CreateInteractionResponse::Modal(modal))
.await;
return true;
}
false
}
pub async fn handle_modal_interaction(ctx: &Context, modal: &ModalInteraction) -> bool {
if !modal.data.custom_id.starts_with(SUGGESTION_MENU) {
return false;
}
let Some((action, owner_id)) = parse_owner_id(&modal.data.custom_id) else {
return false;
};
if modal.user.id.get() != owner_id {
let _ = modal
.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Seul l'auteur du menu peut soumettre ce formulaire.")
.ephemeral(true),
),
)
.await;
return true;
}
let Some(guild_id) = modal.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 current = db::get_or_create_suggestion_settings(&pool, bot_id, guild_id.get() as i64)
.await
.ok();
let Some(settings) = current else {
return true;
};
if action.ends_with(":configure") {
let channel_id = modal_value(modal, "channel_id")
.and_then(|value| value.trim().parse::<i64>().ok());
let approve_channel_id = modal_value(modal, "approve_channel_id")
.and_then(|value| value.trim().parse::<i64>().ok());
if let Ok(updated) = db::update_suggestion_settings(
&pool,
bot_id,
guild_id.get() as i64,
settings.enabled,
channel_id,
approve_channel_id,
)
.await
{
let _ = modal
.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.embed(suggestion_settings_embed(&updated))
.components(suggestion_components(modal.user.id, &updated))
.ephemeral(true),
),
)
.await;
}
return true;
}
if action.ends_with(":submit") {
let content = modal_value(modal, "content").unwrap_or_default();
if content.trim().is_empty() {
let _ = modal
.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Contenu invalide.")
.ephemeral(true),
),
)
.await;
return true;
}
match submit_suggestion(ctx, guild_id, &modal.user, content).await {
Ok(_) => {
let _ = modal
.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Suggestion envoyée.")
.ephemeral(true),
),
)
.await;
}
Err(error) => {
let _ = modal
.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content(error)
.ephemeral(true),
),
)
.await;
}
}
return true;
}
false
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_sync(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_sync(ctx, msg, args).await;
}
pub struct SyncCommand;
pub static COMMAND_DESCRIPTOR: SyncCommand = SyncCommand;
impl crate::commands::command_contract::CommandSpec for SyncCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "sync",
command: "sync",
category: "admin",
params: "<salon/categorie/all>",
summary: "Synchronise les permissions",
description: "Synchronise les permissions d'un salon avec sa categorie, ou tous les salons avec all.",
examples: &["+sync all", "+sync #general"],
alias_source_key: "sync",
default_aliases: &["chsync"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_tempban(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_ban(ctx, msg, args, true).await;
}
pub struct TempbanCommand;
pub static COMMAND_DESCRIPTOR: TempbanCommand = TempbanCommand;
impl crate::commands::command_contract::CommandSpec for TempbanCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "tempban",
command: "tempban",
category: "admin",
params: "<@membre/ID[,..]> <duree> [raison]",
summary: "Ban temporaire",
description: "Ban temporairement un ou plusieurs membres.",
examples: &["+tempban @User 1d"],
alias_source_key: "tempban",
default_aliases: &["tb"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_tempcmute(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_cmute(ctx, msg, args, true).await;
}
pub struct TempcmuteCommand;
pub static COMMAND_DESCRIPTOR: TempcmuteCommand = TempcmuteCommand;
impl crate::commands::command_contract::CommandSpec for TempcmuteCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "tempcmute",
command: "tempcmute",
category: "admin",
params: "<@membre/ID[,..]> <duree> [raison]",
summary: "Mute salon temporaire",
description: "Mute temporaire sur le salon courant.",
examples: &["+tempcmute @User 5m"],
alias_source_key: "tempcmute",
default_aliases: &["tcm"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_tempmute(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_mute(ctx, msg, args, true).await;
}
pub struct TempmuteCommand;
pub static COMMAND_DESCRIPTOR: TempmuteCommand = TempmuteCommand;
impl crate::commands::command_contract::CommandSpec for TempmuteCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "tempmute",
command: "tempmute",
category: "admin",
params: "<@membre/ID[,..]> <duree> [raison]",
summary: "Mute temporaire",
description: "Mute un ou plusieurs membres pour une duree donnee.",
examples: &["+tempmute @User 10m"],
alias_source_key: "tempmute",
default_aliases: &["tm"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_temprole(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_temprole(ctx, msg, args).await;
}
pub struct TempRoleCommand;
pub static COMMAND_DESCRIPTOR: TempRoleCommand = TempRoleCommand;
impl crate::commands::command_contract::CommandSpec for TempRoleCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "temprole",
command: "temprole",
category: "admin",
params: "<membre> <role> <duree>",
summary: "Ajoute un role temporaire",
description: "Attribue un role a un membre pour une duree donnee puis le retire automatiquement.",
examples: &["+temprole @User @VIP 2h"],
alias_source_key: "temprole",
default_aliases: &["trole", "tmprole"],
default_permission: 8,
}
}
}
+428
View File
@@ -0,0 +1,428 @@
use chrono::Utc;
use serenity::builder::{
CreateActionRow, CreateButton, CreateChannel, CreateEmbed, CreateInputText,
CreateInteractionResponse, CreateInteractionResponseMessage, CreateMessage, CreateModal,
};
use serenity::model::application::{
ActionRowComponent, ButtonStyle, ComponentInteraction, InputTextStyle, ModalInteraction,
};
use serenity::model::Colour;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::db;
const TEMPVOC_MENU: &str = "tempvoc:settings";
fn parse_owner_id(custom_id: &str) -> Option<(String, u64)> {
let mut parts = custom_id.rsplitn(2, ':');
let owner = parts.next()?.parse::<u64>().ok()?;
let action = parts.next()?.to_string();
Some((action, owner))
}
fn modal_value(modal: &ModalInteraction, wanted_id: &str) -> Option<String> {
for row in &modal.data.components {
for component in &row.components {
if let ActionRowComponent::InputText(input) = component {
if input.custom_id == wanted_id {
return input.value.clone();
}
}
}
}
None
}
fn tempvoc_embed(settings: &db::TempvocSettings) -> CreateEmbed {
let mut embed = CreateEmbed::new()
.title("Tempvoc")
.description("Gère les vocaux temporaires du serveur.")
.colour(Colour::from_rgb(100, 180, 255))
.timestamp(Utc::now())
.field("Statut", if settings.enabled { "Actif" } else { "Inactif" }, true);
if let Some(trigger) = settings.trigger_channel_id {
embed = embed.field("Canal déclencheur", format!("<#{}>", trigger), true);
}
if let Some(category) = settings.category_id {
embed = embed.field("Catégorie", format!("<#{}>", category), true);
}
embed
}
fn tempvoc_components(owner_id: UserId, settings: &db::TempvocSettings) -> Vec<CreateActionRow> {
let toggle_label = if settings.enabled { "Désactiver" } else { "Activer" };
vec![CreateActionRow::Buttons(vec![
CreateButton::new(format!("{}:toggle:{}", TEMPVOC_MENU, owner_id.get()))
.label(toggle_label)
.style(ButtonStyle::Primary),
CreateButton::new(format!("{}:configure:{}", TEMPVOC_MENU, owner_id.get()))
.label("Configurer")
.style(ButtonStyle::Secondary),
CreateButton::new(format!("{}:refresh:{}", TEMPVOC_MENU, owner_id.get()))
.label("Rafraîchir")
.style(ButtonStyle::Success),
])]
}
async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
let data = ctx.data.read().await;
data.get::<db::DbPoolKey>().cloned()
}
async fn show_menu(ctx: &Context, msg: &Message) {
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 settings = db::get_or_create_tempvoc_settings(&pool, bot_id, guild_id.get() as i64)
.await
.unwrap_or(db::TempvocSettings {
bot_id,
guild_id: guild_id.get() as i64,
trigger_channel_id: None,
category_id: None,
enabled: false,
updated_at: chrono::Utc::now(),
});
let _ = msg
.channel_id
.send_message(
&ctx.http,
CreateMessage::new()
.embed(tempvoc_embed(&settings))
.components(tempvoc_components(msg.author.id, &settings)),
)
.await;
}
pub async fn handle_tempvoc(ctx: &Context, msg: &Message, _args: &[&str]) {
show_menu(ctx, msg).await;
}
pub async fn handle_component_interaction(ctx: &Context, component: &ComponentInteraction) -> bool {
if !component.data.custom_id.starts_with(TEMPVOC_MENU) {
return false;
}
let Some((action, owner_id)) = parse_owner_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 du menu peut l'utiliser.")
.ephemeral(true),
),
)
.await;
return true;
}
let Some(guild_id) = component.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 = db::get_or_create_tempvoc_settings(&pool, bot_id, guild_id.get() as i64)
.await
.ok();
let Some(settings) = settings else {
return true;
};
if action.ends_with(":configure") {
let modal = CreateModal::new(component.data.custom_id.clone(), "Configurer Tempvoc")
.components(vec![
CreateActionRow::InputText(
CreateInputText::new(
InputTextStyle::Short,
"Canal déclencheur",
"trigger_channel_id",
)
.required(false),
),
CreateActionRow::InputText(
CreateInputText::new(InputTextStyle::Short, "Catégorie", "category_id")
.required(false),
),
]);
let _ = component
.create_response(&ctx.http, CreateInteractionResponse::Modal(modal))
.await;
return true;
}
if action.ends_with(":toggle") {
let new_settings = db::update_tempvoc_settings(
&pool,
bot_id,
guild_id.get() as i64,
settings.trigger_channel_id,
settings.category_id,
!settings.enabled,
)
.await
.ok();
if let Some(updated) = new_settings {
let _ = component
.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.embed(tempvoc_embed(&updated))
.components(tempvoc_components(component.user.id, &updated)),
),
)
.await;
}
return true;
}
if action.ends_with(":refresh") {
let _ = component
.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.embed(tempvoc_embed(&settings))
.components(tempvoc_components(component.user.id, &settings)),
),
)
.await;
return true;
}
false
}
pub async fn handle_modal_interaction(ctx: &Context, modal: &ModalInteraction) -> bool {
if !modal.data.custom_id.starts_with(TEMPVOC_MENU) {
return false;
}
let Some((action, owner_id)) = parse_owner_id(&modal.data.custom_id) else {
return false;
};
if modal.user.id.get() != owner_id {
let _ = modal
.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Seul l'auteur du menu peut soumettre ce formulaire.")
.ephemeral(true),
),
)
.await;
return true;
}
if !action.contains(":configure") {
return false;
}
let Some(guild_id) = modal.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 trigger_channel_id = modal_value(modal, "trigger_channel_id")
.and_then(|value| value.trim().parse::<i64>().ok());
let category_id = modal_value(modal, "category_id")
.and_then(|value| value.trim().parse::<i64>().ok());
let updated = db::update_tempvoc_settings(
&pool,
bot_id,
guild_id.get() as i64,
trigger_channel_id,
category_id,
true,
)
.await
.ok();
if let Some(updated) = updated {
let _ = modal
.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.embed(tempvoc_embed(&updated))
.components(tempvoc_components(modal.user.id, &updated))
.ephemeral(true),
),
)
.await;
}
true
}
fn sanitize_voice_name(input: &str) -> String {
let mut out = String::new();
let mut previous_dash = false;
for ch in input.to_lowercase().chars() {
if ch.is_ascii_alphanumeric() {
out.push(ch);
previous_dash = false;
} else if (ch.is_whitespace() || ch == '-' || ch == '_') && !previous_dash {
out.push('-');
previous_dash = true;
}
}
out.trim_matches('-').to_string()
}
async fn cached_room_members(ctx: &Context, guild_id: GuildId, channel_id: ChannelId) -> usize {
ctx.cache
.guild(guild_id)
.map(|guild| {
guild
.voice_states
.values()
.filter(|state| state.channel_id == Some(channel_id))
.count()
})
.unwrap_or(0)
}
async fn create_temp_channel(ctx: &Context, guild_id: GuildId, user: &User, settings: &db::TempvocSettings) {
let Some(trigger_channel_id) = settings.trigger_channel_id else {
return;
};
let Ok(trigger_channel) = ChannelId::new(trigger_channel_id as u64)
.to_channel(&ctx.http)
.await else {
return;
};
let Channel::Guild(trigger) = trigger_channel else {
return;
};
let category_id = settings
.category_id
.map(|value| ChannelId::new(value as u64))
.or(trigger.parent_id);
let name = sanitize_voice_name(&user.name);
if name.is_empty() {
return;
}
let mut builder = CreateChannel::new(format!("🎤 {}", name))
.kind(ChannelType::Voice)
.permissions(trigger.permission_overwrites.clone());
if let Some(category_id) = category_id {
builder = builder.category(category_id);
}
let Ok(channel) = guild_id.create_channel(&ctx.http, builder).await else {
return;
};
if guild_id
.move_member(&ctx.http, user.id, channel.id)
.await
.is_err()
{
let _ = channel.delete(&ctx.http).await;
return;
}
if let Some(pool) = pool(ctx).await {
let _ = db::create_tempvoc_room(
&pool,
settings.bot_id,
settings.guild_id,
channel.id.get() as i64,
user.id.get() as i64,
)
.await;
}
}
async fn delete_temp_channel(ctx: &Context, channel_id: ChannelId) {
if let Ok(channel) = channel_id.to_channel(&ctx.http).await {
if let Channel::Guild(guild_channel) = channel {
let _ = guild_channel.delete(&ctx.http).await;
}
}
}
pub async fn handle_voice_state_update(ctx: &Context, old: Option<&VoiceState>, new: &VoiceState) {
let Some(guild_id) = new.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_i64 = guild_id.get() as i64;
let settings = db::get_or_create_tempvoc_settings(&pool, bot_id, guild_id_i64)
.await
.ok();
let Some(settings) = settings else {
return;
};
let old_channel = old.and_then(|state| state.channel_id);
let new_channel = new.channel_id;
if settings.enabled
&& settings.trigger_channel_id.is_some()
&& new_channel.map(|channel| channel.get() as i64) == settings.trigger_channel_id
{
if let Ok(member) = guild_id.member(&ctx.http, new.user_id).await {
create_temp_channel(ctx, guild_id, &member.user, &settings).await;
}
}
if let Some(old_channel) = old_channel {
if db::get_tempvoc_room_by_channel(&pool, old_channel.get() as i64)
.await
.ok()
.flatten()
.is_some()
&& cached_room_members(ctx, guild_id, old_channel).await == 0
{
delete_temp_channel(ctx, old_channel).await;
let _ = db::delete_tempvoc_room(&pool, old_channel.get() as i64).await;
}
}
}
+17
View File
@@ -0,0 +1,17 @@
use chrono::Utc;
use serenity::builder::CreateEmbed;
use serenity::model::Colour;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::common::send_embed;
pub async fn handle_tempvoc_cmd(ctx: &Context, msg: &Message, _args: &[&str]) {
let embed = CreateEmbed::new()
.title("Commandes Tempvoc")
.description("\n+tempvoc\n+tempvoc cmd\n\nLe salon de création est surveillé automatiquement: lorsqu'un membre le rejoint, un vocal temporaire est créé et l'utilisateur y est déplacé.")
.colour(Colour::from_rgb(100, 180, 255))
.timestamp(Utc::now());
send_embed(ctx, msg, embed).await;
}
+433
View File
@@ -0,0 +1,433 @@
use chrono::Utc;
use serenity::builder::{
CreateActionRow, CreateButton, CreateChannel, CreateEmbed, CreateInputText,
CreateInteractionResponse, CreateInteractionResponseMessage, CreateMessage, CreateModal,
};
use serenity::model::application::{
ActionRowComponent, ButtonStyle, ComponentInteraction, InputTextStyle, ModalInteraction,
};
use serenity::all::{PermissionOverwrite, PermissionOverwriteType, Permissions};
use serenity::model::Colour;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::db;
const TICKET_MENU: &str = "ticket:settings";
fn parse_owner_id(custom_id: &str) -> Option<(String, u64)> {
let mut parts = custom_id.rsplitn(2, ':');
let owner = parts.next()?.parse::<u64>().ok()?;
let action = parts.next()?.to_string();
Some((action, owner))
}
fn modal_value(modal: &ModalInteraction, wanted_id: &str) -> Option<String> {
for row in &modal.data.components {
for component in &row.components {
if let ActionRowComponent::InputText(input) = component {
if input.custom_id == wanted_id {
return input.value.clone();
}
}
}
}
None
}
fn sanitize_channel_name(input: &str) -> String {
let mut out = String::new();
let mut previous_dash = false;
for ch in input.to_lowercase().chars() {
if ch.is_ascii_alphanumeric() {
out.push(ch);
previous_dash = false;
} else if (ch.is_whitespace() || ch == '-' || ch == '_') && !previous_dash {
out.push('-');
previous_dash = true;
}
}
out.trim_matches('-').to_string()
}
fn ticket_embed(settings: &db::TicketSettings) -> CreateEmbed {
let mut embed = CreateEmbed::new()
.title("Gestion des tickets")
.description("Utilise les boutons ci-dessous pour gérer le système de tickets.")
.colour(Colour::from_rgb(90, 160, 255))
.timestamp(Utc::now())
.field("Statut", if settings.enabled { "Actif" } else { "Inactif" }, true);
if let Some(category_id) = settings.category_id {
embed = embed.field("Catégorie", format!("<#{}>", category_id), true);
}
if let Some(log_channel_id) = settings.log_channel_id {
embed = embed.field("Logs", format!("<#{}>", log_channel_id), true);
}
embed
}
fn ticket_components(owner_id: UserId, settings: &db::TicketSettings) -> Vec<CreateActionRow> {
let toggle_label = if settings.enabled { "Désactiver" } else { "Activer" };
vec![CreateActionRow::Buttons(vec![
CreateButton::new(format!("{}:create:{}", TICKET_MENU, owner_id.get()))
.label("Créer")
.style(ButtonStyle::Success),
CreateButton::new(format!("{}:configure:{}", TICKET_MENU, owner_id.get()))
.label("Configurer")
.style(ButtonStyle::Secondary),
CreateButton::new(format!("{}:toggle:{}", TICKET_MENU, owner_id.get()))
.label(toggle_label)
.style(ButtonStyle::Primary),
CreateButton::new(format!("{}:refresh:{}", TICKET_MENU, owner_id.get()))
.label("Rafraîchir")
.style(ButtonStyle::Secondary),
])]
}
async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
let data = ctx.data.read().await;
data.get::<db::DbPoolKey>().cloned()
}
async fn current_settings(ctx: &Context, guild_id: GuildId) -> Option<db::TicketSettings> {
let pool = pool(ctx).await?;
let bot_id = ctx.cache.current_user().id.get() as i64;
db::get_or_create_ticket_settings(&pool, bot_id, guild_id.get() as i64)
.await
.ok()
}
async fn show_menu(ctx: &Context, msg: &Message) {
let Some(guild_id) = msg.guild_id else {
return;
};
let Some(settings) = current_settings(ctx, guild_id).await else {
return;
};
let _ = msg
.channel_id
.send_message(
&ctx.http,
CreateMessage::new()
.embed(ticket_embed(&settings))
.components(ticket_components(msg.author.id, &settings)),
)
.await;
}
async fn create_ticket_channel(
ctx: &Context,
guild_id: GuildId,
creator: UserId,
title: String,
settings: &db::TicketSettings,
) -> Result<ChannelId, String> {
let pool = pool(ctx).await.ok_or_else(|| "Base de données indisponible".to_string())?;
let name = sanitize_channel_name(&title);
if name.is_empty() {
return Err("Nom de ticket invalide".to_string());
}
let mut builder = CreateChannel::new(format!("ticket-{}", name))
.kind(ChannelType::Text)
.permissions(vec![
PermissionOverwrite {
allow: Permissions::empty(),
deny: Permissions::VIEW_CHANNEL.union(
Permissions::SEND_MESSAGES
| Permissions::READ_MESSAGE_HISTORY
| Permissions::ATTACH_FILES
| Permissions::EMBED_LINKS,
),
kind: PermissionOverwriteType::Role(RoleId::new(guild_id.get())),
},
PermissionOverwrite {
allow: Permissions::VIEW_CHANNEL
| Permissions::SEND_MESSAGES
| Permissions::READ_MESSAGE_HISTORY
| Permissions::ATTACH_FILES
| Permissions::EMBED_LINKS
| Permissions::ADD_REACTIONS,
deny: Permissions::empty(),
kind: PermissionOverwriteType::Member(creator),
},
]);
if let Some(category_id) = settings.category_id {
builder = builder.category(ChannelId::new(category_id as u64));
}
let channel = guild_id
.create_channel(&ctx.http, builder)
.await
.map_err(|e| format!("Impossible de créer le salon: {e}"))?;
let _ = db::create_ticket(
&pool,
settings.bot_id,
settings.guild_id,
channel.id.get() as i64,
creator.get() as i64,
title,
)
.await;
Ok(channel.id)
}
pub async fn handle_ticket_settings(ctx: &Context, msg: &Message, _args: &[&str]) {
show_menu(ctx, msg).await;
}
pub async fn handle_component_interaction(ctx: &Context, component: &ComponentInteraction) -> bool {
if !component.data.custom_id.starts_with(TICKET_MENU) {
return false;
}
let Some((action, owner_id)) = parse_owner_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 du menu peut l'utiliser.")
.ephemeral(true),
),
)
.await;
return true;
}
let Some(guild_id) = component.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 = db::get_or_create_ticket_settings(&pool, bot_id, guild_id.get() as i64)
.await
.ok();
let Some(settings) = settings else {
return true;
};
if action.ends_with(":refresh") {
let _ = component
.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.embed(ticket_embed(&settings))
.components(ticket_components(component.user.id, &settings)),
),
)
.await;
return true;
}
if action.ends_with(":toggle") {
if let Ok(updated) = db::update_ticket_settings(
&pool,
bot_id,
guild_id.get() as i64,
settings.category_id,
settings.log_channel_id,
!settings.enabled,
)
.await
{
let _ = component
.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.embed(ticket_embed(&updated))
.components(ticket_components(component.user.id, &updated)),
),
)
.await;
}
return true;
}
if action.ends_with(":configure") {
let modal = CreateModal::new(component.data.custom_id.clone(), "Configurer les tickets")
.components(vec![
CreateActionRow::InputText(
CreateInputText::new(InputTextStyle::Short, "Catégorie", "category_id")
.required(false),
),
CreateActionRow::InputText(
CreateInputText::new(InputTextStyle::Short, "Salon de logs", "log_channel_id")
.required(false),
),
]);
let _ = component
.create_response(&ctx.http, CreateInteractionResponse::Modal(modal))
.await;
return true;
}
if action.ends_with(":create") {
let modal = CreateModal::new(component.data.custom_id.clone(), "Créer un ticket")
.components(vec![CreateActionRow::InputText(
CreateInputText::new(InputTextStyle::Short, "Nom du ticket", "ticket_title")
.required(true)
.max_length(100),
)]);
let _ = component
.create_response(&ctx.http, CreateInteractionResponse::Modal(modal))
.await;
return true;
}
false
}
pub async fn handle_modal_interaction(ctx: &Context, modal: &ModalInteraction) -> bool {
if !modal.data.custom_id.starts_with(TICKET_MENU) {
return false;
}
let Some((action, owner_id)) = parse_owner_id(&modal.data.custom_id) else {
return false;
};
if modal.user.id.get() != owner_id {
let _ = modal
.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Seul l'auteur du menu peut soumettre ce formulaire.")
.ephemeral(true),
),
)
.await;
return true;
}
let Some(guild_id) = modal.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 current = db::get_or_create_ticket_settings(&pool, bot_id, guild_id.get() as i64)
.await
.ok();
let Some(settings) = current else {
return true;
};
if action.ends_with(":configure") {
let category_id = modal_value(modal, "category_id")
.and_then(|value| value.trim().parse::<i64>().ok());
let log_channel_id = modal_value(modal, "log_channel_id")
.and_then(|value| value.trim().parse::<i64>().ok());
if let Ok(updated) = db::update_ticket_settings(
&pool,
bot_id,
guild_id.get() as i64,
category_id,
log_channel_id,
settings.enabled,
)
.await
{
let _ = modal
.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.embed(ticket_embed(&updated))
.components(ticket_components(modal.user.id, &updated))
.ephemeral(true),
),
)
.await;
}
return true;
}
if action.ends_with(":create") {
let title = modal_value(modal, "ticket_title").unwrap_or_default();
let title = title.trim().to_string();
if title.is_empty() {
let _ = modal
.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Nom de ticket invalide.")
.ephemeral(true),
),
)
.await;
return true;
}
match create_ticket_channel(ctx, guild_id, modal.user.id, title.clone(), &settings).await {
Ok(channel_id) => {
let _ = modal
.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.embed(
CreateEmbed::new()
.title("Ticket créé")
.description(format!("Salon: <#{}>", channel_id.get()))
.colour(Colour::from_rgb(0, 200, 120))
.timestamp(Utc::now()),
)
.ephemeral(true),
),
)
.await;
}
Err(error) => {
let _ = modal
.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content(error)
.ephemeral(true),
),
)
.await;
}
}
return true;
}
false
}
+147
View File
@@ -0,0 +1,147 @@
use chrono::Utc;
use serenity::all::{PermissionOverwrite, PermissionOverwriteType, Permissions};
use serenity::builder::{CreateEmbed, EditChannel};
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::admin_common::parse_user_id;
use crate::commands::common::send_embed;
use crate::db;
const TICKET_ALLOW: Permissions = Permissions::VIEW_CHANNEL
.union(Permissions::SEND_MESSAGES)
.union(Permissions::READ_MESSAGE_HISTORY)
.union(Permissions::ATTACH_FILES)
.union(Permissions::EMBED_LINKS)
.union(Permissions::ADD_REACTIONS);
fn ticket_member_id(args: &[&str], msg: &Message) -> Option<UserId> {
msg.mentions.first().map(|user| user.id).or_else(|| {
args.first().and_then(|value| parse_user_id(value))
})
}
async fn ticket_member_update(
ctx: &Context,
msg: &Message,
args: &[&str],
allow: bool,
) -> Result<(), ()> {
let Some(guild_id) = msg.guild_id else {
return Err(());
};
let Some(user_id) = ticket_member_id(args, msg) else {
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Erreur")
.description("Utilisateur introuvable.")
.color(0xED4245),
)
.await;
return Err(());
};
let Some(pool) = ({
let data = ctx.data.read().await;
data.get::<db::DbPoolKey>().cloned()
}) else {
return Err(());
};
let bot_id = ctx.cache.current_user().id.get() as i64;
let guild_id_i64 = guild_id.get() as i64;
let channel_id = msg.channel_id.get() as i64;
let Some(ticket) = db::get_ticket_by_channel(&pool, bot_id, guild_id_i64, channel_id)
.await
.ok()
.flatten()
else {
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Erreur")
.description("Ce salon n'est pas reconnu comme un ticket.")
.color(0xED4245),
)
.await;
return Err(());
};
let Ok(channel) = msg.channel_id.to_channel(&ctx.http).await else {
return Err(());
};
let Channel::Guild(guild_channel) = channel else {
return Err(());
};
let mut overwrites = guild_channel.permission_overwrites.clone();
overwrites.retain(|overwrite| {
!matches!(overwrite.kind, PermissionOverwriteType::Member(id) if id == user_id)
});
if allow {
overwrites.push(PermissionOverwrite {
allow: TICKET_ALLOW,
deny: Permissions::empty(),
kind: PermissionOverwriteType::Member(user_id),
});
}
if msg
.channel_id
.edit(&ctx.http, EditChannel::new().permissions(overwrites))
.await
.is_err()
{
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Erreur")
.description("Impossible de mettre à jour les permissions du ticket.")
.color(0xED4245),
)
.await;
return Err(());
}
if allow {
let _ = db::add_ticket_member(&pool, ticket.id, user_id.get() as i64).await;
} else {
let _ = db::remove_ticket_member(&pool, ticket.id, user_id.get() as i64).await;
}
let title = if allow { "Membre ajouté" } else { "Membre retiré" };
let description = if allow {
format!("<@{}> a été ajouté au ticket.", user_id.get())
} else {
format!("<@{}> a été retiré du ticket.", user_id.get())
};
send_embed(
ctx,
msg,
CreateEmbed::new()
.title(title)
.description(description)
.colour(Colour::from_rgb(0, 200, 120))
.timestamp(Utc::now()),
)
.await;
Ok(())
}
pub async fn handle_ticket_add(ctx: &Context, msg: &Message, args: &[&str]) {
let _ = ticket_member_update(ctx, msg, args, true).await;
}
pub async fn handle_ticket_remove(ctx: &Context, msg: &Message, args: &[&str]) {
let _ = ticket_member_update(ctx, msg, args, false).await;
}
+74
View File
@@ -0,0 +1,74 @@
use chrono::Utc;
use serenity::builder::{CreateEmbed, CreateEmbedFooter};
use serenity::model::Colour;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::common::send_embed;
use crate::db;
pub async fn handle_tickets(ctx: &Context, msg: &Message, args: &[&str]) {
let Some(guild_id) = msg.guild_id else {
return;
};
let page = args
.first()
.and_then(|value| value.parse::<i64>().ok())
.unwrap_or(1)
.max(1);
let Some(pool) = ({
let data = ctx.data.read().await;
data.get::<db::DbPoolKey>().cloned()
}) else {
return;
};
let limit = 10i64;
let offset = (page - 1) * limit;
let bot_id = ctx.cache.current_user().id.get() as i64;
let tickets = db::get_guild_tickets(
&pool,
bot_id,
guild_id.get() as i64,
limit,
offset,
)
.await
.unwrap_or_default();
if tickets.is_empty() {
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Tickets")
.description("Aucun ticket trouvé.")
.colour(Colour::from_rgb(100, 100, 100)),
)
.await;
return;
}
let mut description = String::new();
for ticket in tickets {
description.push_str(&format!(
"**#{} - {}**\nAuteur: <@{}> | Statut: {}\n\n",
ticket.id, ticket.title, ticket.creator_id, ticket.status
));
}
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Tickets")
.description(description)
.colour(Colour::from_rgb(0, 100, 200))
.footer(CreateEmbedFooter::new(format!("Page {}", page)))
.timestamp(Utc::now()),
)
.await;
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_unban(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_unban(ctx, msg, args).await;
}
pub struct UnbanCommand;
pub static COMMAND_DESCRIPTOR: UnbanCommand = UnbanCommand;
impl crate::commands::command_contract::CommandSpec for UnbanCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "unban",
command: "unban",
category: "admin",
params: "<@membre/ID[,..]>",
summary: "Retire un ban",
description: "Unban un ou plusieurs membres.",
examples: &["+unban @User"],
alias_source_key: "unban",
default_aliases: &["ub"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_unbanall(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_unbanall(ctx, msg, args).await;
}
pub struct UnbanAllCommand;
pub static COMMAND_DESCRIPTOR: UnbanAllCommand = UnbanAllCommand;
impl crate::commands::command_contract::CommandSpec for UnbanAllCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "unbanall",
command: "unbanall",
category: "admin",
params: "aucun",
summary: "Retire tous les bannissements",
description: "Supprime tous les bans du serveur cible.",
examples: &["+unbanall"],
alias_source_key: "unbanall",
default_aliases: &["uball", "clearbans"],
default_permission: 8,
}
}
}
+80
View File
@@ -0,0 +1,80 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::admin_common::{ensure_owner, parse_user_id};
use crate::commands::common::send_embed;
use crate::db::{DbPoolKey, remove_from_blacklist};
pub async fn handle_unbl(ctx: &Context, msg: &Message, args: &[&str]) {
if ensure_owner(ctx, msg).await.is_err() {
return;
}
if args.is_empty() {
let embed = serenity::builder::CreateEmbed::new()
.title("Erreur")
.description("Usage: `+unbl <@membre/ID>`")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
}
let Some(target) = parse_user_id(args[0]) else {
let embed = serenity::builder::CreateEmbed::new()
.title("Erreur")
.description("Membre invalide.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
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 {
let embed = serenity::builder::CreateEmbed::new()
.title("Erreur")
.description("DB indisponible.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let count = remove_from_blacklist(&pool, bot_id, target)
.await
.unwrap_or(0);
let desc = if count > 0 {
format!("<@{}> retiré de la blacklist.", target.get())
} else {
format!("<@{}> n'était pas blacklisté.", target.get())
};
let embed = serenity::builder::CreateEmbed::new()
.title("Unblacklist")
.description(desc)
.color(0x57F287);
send_embed(ctx, msg, embed).await;
}
pub struct UnblCommand;
pub static COMMAND_DESCRIPTOR: UnblCommand = UnblCommand;
impl crate::commands::command_contract::CommandSpec for UnblCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "unbl",
command: "unbl",
category: "admin",
params: "<@membre/ID>",
summary: "Retire un utilisateur blacklist",
description: "Retire un utilisateur de la blacklist globale du bot.",
examples: &["+unbl", "+ul", "+help unbl"],
alias_source_key: "unbl",
default_aliases: &["unb"],
default_permission: 9,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_uncmute(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_uncmute(ctx, msg, args).await;
}
pub struct UncmuteCommand;
pub static COMMAND_DESCRIPTOR: UncmuteCommand = UncmuteCommand;
impl crate::commands::command_contract::CommandSpec for UncmuteCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "uncmute",
command: "uncmute",
category: "admin",
params: "<@membre/ID[,..]>",
summary: "Retire un cmute",
description: "Met fin au mute salon.",
examples: &["+uncmute @User"],
alias_source_key: "uncmute",
default_aliases: &["ucm"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_unhide(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_hide_unhide(ctx, msg, args, false).await;
}
pub struct UnhideCommand;
pub static COMMAND_DESCRIPTOR: UnhideCommand = UnhideCommand;
impl crate::commands::command_contract::CommandSpec for UnhideCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "unhide",
command: "unhide",
category: "admin",
params: "[salon]",
summary: "Affiche un salon",
description: "Rend a nouveau visible un salon.",
examples: &["+unhide", "+unhide #general"],
alias_source_key: "unhide",
default_aliases: &["uhd"],
default_permission: 8,
}
}
}
+27
View File
@@ -0,0 +1,27 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_unhideall(ctx: &Context, msg: &Message) {
moderation_tools::handle_hideall_unhideall(ctx, msg, false).await;
}
pub struct UnhideallCommand;
pub static COMMAND_DESCRIPTOR: UnhideallCommand = UnhideallCommand;
impl crate::commands::command_contract::CommandSpec for UnhideallCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "unhideall",
command: "unhideall",
category: "admin",
params: "aucun",
summary: "Affiche tous les salons",
description: "Rend visibles tous les salons du serveur.",
examples: &["+unhideall"],
alias_source_key: "unhideall",
default_aliases: &["uhda"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_unlock(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_lock_unlock(ctx, msg, args, false).await;
}
pub struct UnlockCommand;
pub static COMMAND_DESCRIPTOR: UnlockCommand = UnlockCommand;
impl crate::commands::command_contract::CommandSpec for UnlockCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "unlock",
command: "unlock",
category: "admin",
params: "[salon]",
summary: "Ouvre un salon",
description: "Deverrouille un salon texte ou vocal.",
examples: &["+unlock", "+unlock #general"],
alias_source_key: "unlock",
default_aliases: &["ulk"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_unlockall(ctx: &Context, msg: &Message) {
moderation_tools::handle_lockall_unlockall(ctx, msg, false).await;
}
pub struct UnlockallCommand;
pub static COMMAND_DESCRIPTOR: UnlockallCommand = UnlockallCommand;
impl crate::commands::command_contract::CommandSpec for UnlockallCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "unlockall",
command: "unlockall",
category: "admin",
params: "aucun",
summary: "Ouvre tous les salons",
description: "Deverrouille tous les salons du serveur.",
examples: &["+unlockall"],
alias_source_key: "unlockall",
default_aliases: &["ulka"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_unmassiverole(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_massive_role(ctx, msg, args, false).await;
}
pub struct UnMassiveRoleCommand;
pub static COMMAND_DESCRIPTOR: UnMassiveRoleCommand = UnMassiveRoleCommand;
impl crate::commands::command_contract::CommandSpec for UnMassiveRoleCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "unmassiverole",
command: "unmassiverole",
category: "admin",
params: "<role_cible> [role_filtre]",
summary: "Retire un role en masse",
description: "Retire un role a tous les membres ou a ceux qui ont un role filtre.",
examples: &["+unmassiverole @VIP", "+unmassiverole @VIP @Membres"],
alias_source_key: "unmassiverole",
default_aliases: &["umrole", "umr"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_unmute(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_unmute(ctx, msg, args).await;
}
pub struct UnmuteCommand;
pub static COMMAND_DESCRIPTOR: UnmuteCommand = UnmuteCommand;
impl crate::commands::command_contract::CommandSpec for UnmuteCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "unmute",
command: "unmute",
category: "admin",
params: "<@membre/ID[,..]>",
summary: "Retire un mute",
description: "Met fin au mute d un ou plusieurs membres.",
examples: &["+unmute @User"],
alias_source_key: "unmute",
default_aliases: &["um"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_unmuteall(ctx: &Context, msg: &Message) {
moderation_tools::handle_unmuteall(ctx, msg).await;
}
pub struct UnmuteallCommand;
pub static COMMAND_DESCRIPTOR: UnmuteallCommand = UnmuteallCommand;
impl crate::commands::command_contract::CommandSpec for UnmuteallCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "unmuteall",
command: "unmuteall",
category: "admin",
params: "aucun",
summary: "Retire tous les mutes",
description: "Supprime tous les mutes en cours.",
examples: &["+unmuteall"],
alias_source_key: "unmuteall",
default_aliases: &["uma"],
default_permission: 8,
}
}
}
+89
View File
@@ -0,0 +1,89 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::admin_common::{app_owner_id, ensure_owner, parse_user_id};
use crate::commands::common::send_embed;
use crate::db::{DbPoolKey, remove_bot_owner};
pub async fn handle_unowner(ctx: &Context, msg: &Message, args: &[&str]) {
if ensure_owner(ctx, msg).await.is_err() {
return;
}
if args.is_empty() {
let embed = serenity::builder::CreateEmbed::new()
.title("Erreur")
.description("Usage: `+unowner <@membre/ID>`")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
}
let Some(target) = parse_user_id(args[0]) else {
let embed = serenity::builder::CreateEmbed::new()
.title("Erreur")
.description("Membre invalide.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
if let Some(app_owner) = app_owner_id(ctx).await {
if app_owner == target {
let embed = serenity::builder::CreateEmbed::new()
.title("Refusé")
.description("Impossible de retirer l'owner principal de l'application.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
}
}
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 {
let embed = serenity::builder::CreateEmbed::new()
.title("Erreur")
.description("DB indisponible.")
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
let removed = remove_bot_owner(&pool, bot_id, target).await.unwrap_or(0);
let desc = if removed > 0 {
format!("<@{}> n'est plus owner.", target.get())
} else {
format!("<@{}> n'était pas owner.", target.get())
};
let embed = serenity::builder::CreateEmbed::new()
.title("Unowner")
.description(desc)
.color(0x57F287);
send_embed(ctx, msg, embed).await;
}
pub struct UnownerCommand;
pub static COMMAND_DESCRIPTOR: UnownerCommand = UnownerCommand;
impl crate::commands::command_contract::CommandSpec for UnownerCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "unowner",
command: "unowner",
category: "admin",
params: "<@membre/ID>",
summary: "Retire un owner du bot",
description: "Retire un utilisateur de la liste des owners supplementaires du bot.",
examples: &["+unowner", "+ur", "+help unowner"],
alias_source_key: "unowner",
default_aliases: &["uow"],
default_permission: 9,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_untemprole(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_untemprole(ctx, msg, args).await;
}
pub struct UnTempRoleCommand;
pub static COMMAND_DESCRIPTOR: UnTempRoleCommand = UnTempRoleCommand;
impl crate::commands::command_contract::CommandSpec for UnTempRoleCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "untemprole",
command: "untemprole",
category: "admin",
params: "<membre> <role>",
summary: "Retire un role temporaire",
description: "Retire immediatement un role temporaire et desactive son expiration.",
examples: &["+untemprole @User @VIP"],
alias_source_key: "untemprole",
default_aliases: &["untrole", "deltrole"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_voicekick(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_voicekick(ctx, msg, args).await;
}
pub struct VoiceKickCommand;
pub static COMMAND_DESCRIPTOR: VoiceKickCommand = VoiceKickCommand;
impl crate::commands::command_contract::CommandSpec for VoiceKickCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "voicekick",
command: "voicekick",
category: "admin",
params: "<membre...>",
summary: "Deconnecte des membres du vocal",
description: "Deconnecte un ou plusieurs membres de leur salon vocal actuel.",
examples: &["+voicekick @User", "+voicekick @U1 @U2"],
alias_source_key: "voicekick",
default_aliases: &["vk", "vdisconnect"],
default_permission: 8,
}
}
}
+27
View File
@@ -0,0 +1,27 @@
use crate::commands::logs_service;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_voicelog(ctx: &Context, msg: &Message, args: &[&str]) {
logs_service::handle_log_toggle(ctx, msg, args, "voice", "VoiceLog").await;
}
pub struct VoicelogCommand;
pub static COMMAND_DESCRIPTOR: VoicelogCommand = VoicelogCommand;
impl crate::commands::command_contract::CommandSpec for VoicelogCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "voicelog",
command: "voicelog",
category: "admin",
params: "<on [salon]|off>",
summary: "Active les logs vocaux",
description: "Active ou desactive les logs de l activite vocale.",
examples: &["+voicelog on #logs", "+voicelog off"],
alias_source_key: "voicelog",
default_aliases: &["vlog"],
default_permission: 8,
}
}
}
+28
View File
@@ -0,0 +1,28 @@
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::advanced_tools;
pub async fn handle_voicemove(ctx: &Context, msg: &Message, args: &[&str]) {
advanced_tools::handle_voicemove(ctx, msg, args).await;
}
pub struct VoiceMoveCommand;
pub static COMMAND_DESCRIPTOR: VoiceMoveCommand = VoiceMoveCommand;
impl crate::commands::command_contract::CommandSpec for VoiceMoveCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "voicemove",
command: "voicemove",
category: "admin",
params: "<salon_source> <salon_destination>",
summary: "Deplace les membres vocaux",
description: "Deplace tous les membres d'un salon vocal vers un autre salon.",
examples: &["+voicemove #General #Event"],
alias_source_key: "voicemove",
default_aliases: &["vmove", "vmoveall"],
default_permission: 8,
}
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::commands::moderation_tools;
use serenity::model::prelude::*;
use serenity::prelude::*;
pub async fn handle_warn(ctx: &Context, msg: &Message, args: &[&str]) {
moderation_tools::handle_warn(ctx, msg, args).await;
}
pub struct WarnCommand;
pub static COMMAND_DESCRIPTOR: WarnCommand = WarnCommand;
impl crate::commands::command_contract::CommandSpec for WarnCommand {
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
crate::commands::command_contract::CommandMetadata {
key: "warn",
command: "warn",
category: "admin",
params: "<@membre/ID[,..]> [raison]",
summary: "Donne un warn",
description: "Ajoute un warn a un ou plusieurs membres.",
examples: &["+warn @User spam"],
alias_source_key: "warn",
default_aliases: &["avert"],
default_permission: 8,
}
}
}