mirror of
https://github.com/arthur-pbty/shadowbot.git
synced 2026-06-04 15:56:31 +02:00
feat(events): add message update, role, and voice state update handlers
- Implemented `handle_message_update` to log message edits. - Created role event handlers for role creation, update, and deletion. - Added voice state update handling to log channel changes. - Introduced a new `ready_event` handler to restore bot presence and enforce blacklist. - Updated `mod.rs` to include new event modules. - Enhanced `main.rs` for database connection and initialization. - Added comprehensive permission management in `permissions.rs`.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
target
|
||||
.git
|
||||
.gitignore
|
||||
.env
|
||||
.env.*
|
||||
@@ -0,0 +1,11 @@
|
||||
# Discord
|
||||
BOT_TOKEN=change_me
|
||||
FORCE_OWNER_IDS=671763971803447298
|
||||
|
||||
# PostgreSQL
|
||||
POSTGRES_DB=shadowbot
|
||||
POSTGRES_USER=shadowbot
|
||||
POSTGRES_PASSWORD=change_me
|
||||
|
||||
# App database URL
|
||||
DATABASE_URL=postgres://shadowbot:change_me@postgres:5432/shadowbot
|
||||
@@ -0,0 +1,4 @@
|
||||
/target
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
Generated
+3333
File diff suppressed because it is too large
Load Diff
+17
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "shadowbot"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
serenity = { version = "0.12", features = ["client", "gateway", "rustls_backend", "model"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
dotenv = "0.15"
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "chrono"] }
|
||||
chrono = { version = "0.4", features = ["clock"] }
|
||||
meval = "0.2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
rand = "0.8"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
|
||||
base64 = "0.22"
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM rust:1-slim-bookworm AS builder
|
||||
WORKDIR /app
|
||||
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY src ./src
|
||||
|
||||
RUN cargo build --release
|
||||
|
||||
FROM debian:bookworm-slim AS runtime
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& useradd --create-home --uid 10001 appuser
|
||||
|
||||
COPY --from=builder /app/target/release/shadowbot /usr/local/bin/shadowbot
|
||||
|
||||
USER appuser
|
||||
WORKDIR /home/appuser
|
||||
ENV RUST_LOG=info
|
||||
|
||||
CMD ["shadowbot"]
|
||||
@@ -0,0 +1,36 @@
|
||||
services:
|
||||
bot:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: shadowbot:latest
|
||||
container_name: shadowbot-bot
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
BOT_TOKEN: ${BOT_TOKEN}
|
||||
DATABASE_URL: postgres://${POSTGRES_USER:-shadowbot}:${POSTGRES_PASSWORD:-shadowbot_dev_password}@postgres:5432/${POSTGRES_DB:-shadowbot}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: shadowbot-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-shadowbot}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-shadowbot}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-shadowbot_dev_password}
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-shadowbot} -d ${POSTGRES_DB:-shadowbot}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
use serenity::gateway::ActivityData;
|
||||
use serenity::model::prelude::OnlineStatus;
|
||||
use serenity::prelude::{Context, TypeMapKey};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::{Duration, sleep};
|
||||
|
||||
pub struct ActivityTaskKey;
|
||||
|
||||
impl TypeMapKey for ActivityTaskKey {
|
||||
type Value = Arc<Mutex<Option<JoinHandle<()>>>>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum RotatingActivityKind {
|
||||
Playing,
|
||||
Listening,
|
||||
Watching,
|
||||
Competing,
|
||||
Streaming,
|
||||
}
|
||||
|
||||
impl RotatingActivityKind {
|
||||
pub fn from_command(command: &str) -> Option<Self> {
|
||||
match command {
|
||||
"+playto" => Some(Self::Playing),
|
||||
"+listen" => Some(Self::Listening),
|
||||
"+watch" => Some(Self::Watching),
|
||||
"+compet" => Some(Self::Competing),
|
||||
"+stream" => Some(Self::Streaming),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_db(value: &str) -> Option<Self> {
|
||||
match value {
|
||||
"playing" => Some(Self::Playing),
|
||||
"listening" => Some(Self::Listening),
|
||||
"watching" => Some(Self::Watching),
|
||||
"competing" => Some(Self::Competing),
|
||||
"streaming" => Some(Self::Streaming),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_db(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Playing => "playing",
|
||||
Self::Listening => "listening",
|
||||
Self::Watching => "watching",
|
||||
Self::Competing => "competing",
|
||||
Self::Streaming => "streaming",
|
||||
}
|
||||
}
|
||||
|
||||
fn to_activity(self, message: &str) -> ActivityData {
|
||||
match self {
|
||||
Self::Playing => ActivityData::playing(message),
|
||||
Self::Listening => ActivityData::listening(message),
|
||||
Self::Watching => ActivityData::watching(message),
|
||||
Self::Competing => ActivityData::competing(message),
|
||||
Self::Streaming => ActivityData::streaming(message, "https://twitch.tv/discord")
|
||||
.unwrap_or_else(|_| ActivityData::playing(message)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_status(value: &str) -> OnlineStatus {
|
||||
match value {
|
||||
"idle" => OnlineStatus::Idle,
|
||||
"dnd" => OnlineStatus::DoNotDisturb,
|
||||
"invisible" => OnlineStatus::Invisible,
|
||||
_ => OnlineStatus::Online,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stop_rotation(ctx: &Context) {
|
||||
let task_slot = {
|
||||
let mut data = ctx.data.write().await;
|
||||
if !data.contains_key::<ActivityTaskKey>() {
|
||||
data.insert::<ActivityTaskKey>(Arc::new(Mutex::new(None)));
|
||||
}
|
||||
data.get::<ActivityTaskKey>().cloned()
|
||||
};
|
||||
|
||||
if let Some(slot) = task_slot {
|
||||
let mut guard = slot.lock().await;
|
||||
if let Some(handle) = guard.take() {
|
||||
handle.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_rotation(
|
||||
ctx: &Context,
|
||||
kind: RotatingActivityKind,
|
||||
messages: Vec<String>,
|
||||
status: OnlineStatus,
|
||||
) {
|
||||
if messages.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
stop_rotation(ctx).await;
|
||||
|
||||
let task_slot = {
|
||||
let mut data = ctx.data.write().await;
|
||||
if !data.contains_key::<ActivityTaskKey>() {
|
||||
data.insert::<ActivityTaskKey>(Arc::new(Mutex::new(None)));
|
||||
}
|
||||
data.get::<ActivityTaskKey>().cloned()
|
||||
};
|
||||
|
||||
let Some(slot) = task_slot else {
|
||||
return;
|
||||
};
|
||||
|
||||
let cloned_ctx = ctx.clone();
|
||||
let handle = tokio::spawn(async move {
|
||||
let mut index = 0usize;
|
||||
loop {
|
||||
let msg = &messages[index % messages.len()];
|
||||
let activity = kind.to_activity(msg);
|
||||
cloned_ctx.set_presence(Some(activity), status);
|
||||
index = (index + 1) % messages.len();
|
||||
sleep(Duration::from_secs(30)).await;
|
||||
}
|
||||
});
|
||||
|
||||
let mut guard = slot.lock().await;
|
||||
*guard = Some(handle);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::permissions::is_owner_user;
|
||||
|
||||
pub fn parse_user_id(input: &str) -> Option<UserId> {
|
||||
let cleaned = input
|
||||
.trim()
|
||||
.trim_start_matches('<')
|
||||
.trim_end_matches('>')
|
||||
.trim_start_matches('@')
|
||||
.trim_start_matches('!');
|
||||
|
||||
cleaned.parse::<u64>().ok().map(UserId::new)
|
||||
}
|
||||
|
||||
pub async fn app_owner_id(ctx: &Context) -> Option<UserId> {
|
||||
let info = ctx.http.get_current_application_info().await.ok()?;
|
||||
info.owner.map(|u| u.id)
|
||||
}
|
||||
|
||||
pub async fn ensure_owner(ctx: &Context, msg: &Message) -> Result<(), ()> {
|
||||
if is_owner_user(ctx, msg.author.id).await {
|
||||
Ok(())
|
||||
} else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Accès refusé")
|
||||
.description("Cette commande est réservée aux owners du bot.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ban_user_everywhere(ctx: &Context, user_id: UserId, reason: &str) -> (usize, usize) {
|
||||
let guilds = ctx.cache.guilds();
|
||||
let mut ok = 0usize;
|
||||
let mut ko = 0usize;
|
||||
|
||||
for guild_id in guilds {
|
||||
match guild_id
|
||||
.ban_with_reason(&ctx.http, user_id, 0, reason)
|
||||
.await
|
||||
{
|
||||
Ok(_) => ok += 1,
|
||||
Err(_) => ko += 1,
|
||||
}
|
||||
}
|
||||
|
||||
(ok, ko)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::db::{DbPoolKey, is_blacklisted, list_blacklisted_ids};
|
||||
|
||||
pub async fn enforce_blacklist_on_message(ctx: &Context, msg: &Message) -> bool {
|
||||
if msg.author.bot {
|
||||
return false;
|
||||
}
|
||||
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let pool = {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
};
|
||||
|
||||
let Some(pool) = pool else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let blacklisted = is_blacklisted(&pool, bot_id, msg.author.id)
|
||||
.await
|
||||
.unwrap_or(false);
|
||||
if !blacklisted {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(guild_id) = msg.guild_id {
|
||||
let _ = guild_id
|
||||
.ban_with_reason(&ctx.http, msg.author.id, 0, "Blacklist globale du bot")
|
||||
.await;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub async fn enforce_blacklist_on_guild(ctx: &Context, guild_id: GuildId) {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let pool = {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
};
|
||||
|
||||
let Some(pool) = pool else {
|
||||
return;
|
||||
};
|
||||
|
||||
let users = list_blacklisted_ids(&pool, bot_id)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
for uid in users {
|
||||
let _ = guild_id
|
||||
.ban_with_reason(&ctx.http, uid, 0, "Blacklist globale du bot")
|
||||
.await;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,137 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{add_list_fields, send_embed};
|
||||
use crate::db::{
|
||||
DbPoolKey, get_command_alias, list_command_aliases, remove_command_alias, set_command_alias,
|
||||
};
|
||||
use crate::permissions::all_command_keys;
|
||||
|
||||
pub async fn handle_alias(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
if args.len() == 1 {
|
||||
let aliases = list_command_aliases(&pool, bot_id)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let lines = aliases
|
||||
.into_iter()
|
||||
.map(|(alias, command)| format!("`{}` -> `{}`", alias, command))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Aliases")
|
||||
.color(0x5865F2);
|
||||
embed = add_list_fields(embed, &lines, "Aliases enregistrés");
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args.len() < 2 {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+alias <commande> <alias>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args[0].eq_ignore_ascii_case("remove") || args[0].eq_ignore_ascii_case("delete") {
|
||||
let alias_name = args[1].trim_start_matches('+').to_lowercase();
|
||||
if alias_name.is_empty() {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Alias invalide.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let removed = remove_command_alias(&pool, bot_id, &alias_name)
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Alias supprimé")
|
||||
.description(format!("`{}` : {} suppression(s).", alias_name, removed))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let command = args[0].trim_start_matches('+').to_lowercase();
|
||||
if !all_command_keys()
|
||||
.iter()
|
||||
.any(|candidate| candidate == &command)
|
||||
{
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Commande cible inconnue.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let alias_name = args[1].trim_start_matches('+').to_lowercase();
|
||||
if alias_name.is_empty() {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Alias invalide.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let _ = set_command_alias(&pool, bot_id, &alias_name, &command).await;
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Alias créé")
|
||||
.description(format!(
|
||||
"`{}` devient un alias de `{}`",
|
||||
alias_name, command
|
||||
))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
}
|
||||
|
||||
pub async fn resolve_alias(ctx: &Context, command: &str) -> Option<String> {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let pool = pool(ctx).await?;
|
||||
get_command_alias(&pool, bot_id, command)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub async fn resolve_command_alias_name(ctx: &Context, command: &str) -> Option<String> {
|
||||
resolve_alias(ctx, command).await
|
||||
}
|
||||
pub struct AliasCommand;
|
||||
pub static COMMAND_DESCRIPTOR: AliasCommand = AliasCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for AliasCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "alias",
|
||||
command: "alias",
|
||||
category: "permissions",
|
||||
params: "<commande> <alias> | remove <alias> | list",
|
||||
summary: "Gere les aliases personnalises",
|
||||
description: "Liste, ajoute ou supprime des aliases de commandes stockes en base.",
|
||||
examples: &["+alias", "+as", "+help alias"],
|
||||
alias_source_key: "alias",
|
||||
default_aliases: &["als"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::guild::Role;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::commands::common::{add_list_fields, has_flag, mention_user, parse_limit, send_embed};
|
||||
|
||||
pub async fn handle_alladmins(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let limit = parse_limit(args, 25, 100);
|
||||
let detailed = has_flag(args, &["--details", "-d", "full"]);
|
||||
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Commande invalide")
|
||||
.description("Cette commande doit être utilisée dans un serveur.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let partial_guild = match guild_id.to_partial_guild(&ctx.http).await {
|
||||
Ok(guild) => guild,
|
||||
Err(why) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de récupérer le serveur: {why}"))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let members = match guild_id.members(&ctx.http, None, None).await {
|
||||
Ok(members) => members,
|
||||
Err(why) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de récupérer les membres: {why}"))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut admin_members = members
|
||||
.iter()
|
||||
.filter(|member| {
|
||||
!member.user.bot
|
||||
&& has_admin_permission(member, partial_guild.owner_id, &partial_guild.roles)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
admin_members.sort_by_key(|member| member.user.name.to_lowercase());
|
||||
|
||||
if admin_members.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Admins (hors bots)")
|
||||
.description("Aucun administrateur (hors bots) trouvé.")
|
||||
.color(0xFEE75C);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let visible = admin_members.iter().take(limit).collect::<Vec<_>>();
|
||||
let lines = visible
|
||||
.iter()
|
||||
.map(|member| {
|
||||
if detailed {
|
||||
let top_role = member
|
||||
.roles
|
||||
.iter()
|
||||
.filter_map(|role_id| partial_guild.roles.get(role_id))
|
||||
.max_by_key(|role| role.position)
|
||||
.map(|role| role.name.clone())
|
||||
.unwrap_or_else(|| "Aucun".to_string());
|
||||
|
||||
format!(
|
||||
"- {} | ID: {} | Roles: {} | Top: {}",
|
||||
mention_user(member.user.id),
|
||||
member.user.id,
|
||||
member.roles.len(),
|
||||
top_role
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"- {} (ID: {})",
|
||||
mention_user(member.user.id),
|
||||
member.user.id
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let ratio = (admin_members.len() as f64 / members.len() as f64) * 100.0;
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title("Admins (hors bots)")
|
||||
.description(format!(
|
||||
"Serveur: **{}**\nAdmins humains: **{}** / Membres: **{}** ({:.1}%)",
|
||||
partial_guild.name,
|
||||
admin_members.len(),
|
||||
members.len(),
|
||||
ratio
|
||||
))
|
||||
.color(0x5865F2)
|
||||
.field("Owner", partial_guild.owner_id.mention().to_string(), true);
|
||||
|
||||
embed = add_list_fields(
|
||||
embed,
|
||||
&lines,
|
||||
&format!(
|
||||
"Liste ({} affichés / {} total)",
|
||||
visible.len(),
|
||||
admin_members.len()
|
||||
),
|
||||
);
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
fn has_admin_permission(member: &Member, owner_id: UserId, roles: &HashMap<RoleId, Role>) -> bool {
|
||||
if member.user.id == owner_id {
|
||||
return true;
|
||||
}
|
||||
|
||||
member.roles.iter().any(|role_id| {
|
||||
roles
|
||||
.get(role_id)
|
||||
.is_some_and(|role| role.permissions.contains(Permissions::ADMINISTRATOR))
|
||||
})
|
||||
}
|
||||
|
||||
pub struct AlladminsCommand;
|
||||
pub static COMMAND_DESCRIPTOR: AlladminsCommand = AlladminsCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for AlladminsCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "alladmins",
|
||||
command: "alladmins",
|
||||
category: "general",
|
||||
params: "aucun",
|
||||
summary: "Liste les administrateurs du serveur",
|
||||
description: "Affiche les membres qui possedent des droits administrateur sur le serveur.",
|
||||
examples: &["+alladmins", "+as", "+help alladmins"],
|
||||
alias_source_key: "alladmins",
|
||||
default_aliases: &["aad"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{
|
||||
add_list_fields, discord_ts, has_flag, mention_user, parse_limit, send_embed,
|
||||
};
|
||||
|
||||
pub async fn handle_allbots(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let limit = parse_limit(args, 25, 100);
|
||||
let detailed = has_flag(args, &["--details", "-d", "full"]);
|
||||
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Commande invalide")
|
||||
.description("Cette commande doit être utilisée dans un serveur.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let guild = match guild_id.to_partial_guild(&ctx.http).await {
|
||||
Ok(guild) => guild,
|
||||
Err(why) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de récupérer le serveur: {why}"))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let members = match guild_id.members(&ctx.http, None, None).await {
|
||||
Ok(members) => members,
|
||||
Err(why) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de récupérer les membres: {why}"))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut bots = members
|
||||
.iter()
|
||||
.filter(|member| member.user.bot)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
bots.sort_by_key(|member| member.user.name.to_lowercase());
|
||||
|
||||
if bots.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Bots du serveur")
|
||||
.description("Aucun bot trouvé sur ce serveur.")
|
||||
.color(0xFEE75C);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let visible = bots.iter().take(limit).collect::<Vec<_>>();
|
||||
let lines = visible
|
||||
.iter()
|
||||
.map(|member| {
|
||||
if detailed {
|
||||
format!(
|
||||
"- {} | ID: {} | Créé: {}",
|
||||
mention_user(member.user.id),
|
||||
member.user.id,
|
||||
discord_ts(member.user.created_at(), "F")
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"- {} (ID: {})",
|
||||
mention_user(member.user.id),
|
||||
member.user.id
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let bot_ratio = (bots.len() as f64 / members.len() as f64) * 100.0;
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title("Bots présents sur le serveur")
|
||||
.description(format!(
|
||||
"Serveur: **{}**\nBots: **{}** / Membres: **{}** ({:.1}%)",
|
||||
guild.name,
|
||||
bots.len(),
|
||||
members.len(),
|
||||
bot_ratio
|
||||
))
|
||||
.color(0x5865F2);
|
||||
|
||||
if let Some(newest_bot) = bots.iter().max_by_key(|member| member.user.created_at()) {
|
||||
embed = embed.field(
|
||||
"Bot le plus récent",
|
||||
format!(
|
||||
"{} ({})",
|
||||
mention_user(newest_bot.user.id),
|
||||
discord_ts(newest_bot.user.created_at(), "F")
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
embed = add_list_fields(
|
||||
embed,
|
||||
&lines,
|
||||
&format!("Liste ({} affichés / {} total)", visible.len(), bots.len()),
|
||||
);
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub struct AllbotsCommand;
|
||||
pub static COMMAND_DESCRIPTOR: AllbotsCommand = AllbotsCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for AllbotsCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "allbots",
|
||||
command: "allbots",
|
||||
category: "general",
|
||||
params: "aucun",
|
||||
summary: "Liste tous les bots du serveur",
|
||||
description: "Affiche la liste des membres bots presents sur le serveur courant.",
|
||||
examples: &["+allbots", "+as", "+help allbots"],
|
||||
alias_source_key: "allbots",
|
||||
default_aliases: &["abt"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::perms_service;
|
||||
|
||||
pub async fn handle_allperms(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
perms_service::handle_allperms(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct AllpermsCommand;
|
||||
pub static COMMAND_DESCRIPTOR: AllpermsCommand = AllpermsCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for AllpermsCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "allperms",
|
||||
command: "allperms",
|
||||
category: "permissions",
|
||||
params: "[page]",
|
||||
summary: "Liste les ACL de toutes commandes",
|
||||
description: "Affiche le niveau ACL requis pour chaque commande avec pagination.",
|
||||
examples: &["+allperms", "+as", "+help allperms"],
|
||||
alias_source_key: "allperms",
|
||||
default_aliases: &["apm"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
|
||||
pub async fn handle_banner(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let user = if args.is_empty() {
|
||||
msg.author.clone()
|
||||
} else {
|
||||
let user_id = args[0]
|
||||
.trim_start_matches('<')
|
||||
.trim_end_matches('>')
|
||||
.trim_start_matches('@')
|
||||
.trim_start_matches('!')
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.map(UserId::new);
|
||||
|
||||
if let Some(uid) = user_id {
|
||||
match ctx.http.get_user(uid).await {
|
||||
Ok(u) => u,
|
||||
Err(_) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Utilisateur non trouvé.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Impossible de parser l'utilisateur.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let banner_url = user.banner_url().unwrap_or_default();
|
||||
|
||||
if banner_url.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("{} n'a pas de bannière.", user.name))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title(format!("Bannière de {}", user.name))
|
||||
.image(banner_url)
|
||||
.color(0x5865F2);
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub struct BannerCommand;
|
||||
pub static COMMAND_DESCRIPTOR: BannerCommand = BannerCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for BannerCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "banner",
|
||||
command: "banner",
|
||||
category: "general",
|
||||
params: "<@membre/ID>",
|
||||
summary: "Affiche la banniere utilisateur",
|
||||
description: "Affiche la banniere de profil dun utilisateur cible ou de lauteur.",
|
||||
examples: &["+banner", "+br", "+help banner"],
|
||||
alias_source_key: "banner",
|
||||
default_aliases: &["bnr"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{
|
||||
add_list_fields, discord_ts, has_flag, mention_user, parse_limit, send_embed,
|
||||
};
|
||||
|
||||
pub async fn handle_boosters(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let limit = parse_limit(args, 25, 100);
|
||||
let detailed = has_flag(args, &["--details", "-d", "full"]);
|
||||
let recent_first = has_flag(args, &["--recent", "recent", "-r"]);
|
||||
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Commande invalide")
|
||||
.description("Cette commande doit être utilisée dans un serveur.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let guild = match guild_id.to_partial_guild(&ctx.http).await {
|
||||
Ok(guild) => guild,
|
||||
Err(why) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de récupérer le serveur: {why}"))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let members = match guild_id.members(&ctx.http, None, None).await {
|
||||
Ok(members) => members,
|
||||
Err(why) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de récupérer les membres: {why}"))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut boosters = members
|
||||
.iter()
|
||||
.filter(|member| member.premium_since.is_some())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if recent_first {
|
||||
boosters.sort_by(|left, right| right.premium_since.cmp(&left.premium_since));
|
||||
} else {
|
||||
boosters.sort_by_key(|member| member.user.name.to_lowercase());
|
||||
}
|
||||
|
||||
if boosters.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Boosters du serveur")
|
||||
.description("Aucun booster trouvé sur ce serveur.")
|
||||
.color(0xFEE75C);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let visible = boosters.iter().take(limit).collect::<Vec<_>>();
|
||||
let lines = visible
|
||||
.iter()
|
||||
.map(|member| {
|
||||
if detailed {
|
||||
format!(
|
||||
"- {} | ID: {} | Boost depuis: {}",
|
||||
mention_user(member.user.id),
|
||||
member.user.id,
|
||||
member
|
||||
.premium_since
|
||||
.map(|value| discord_ts(value, "F"))
|
||||
.unwrap_or_else(|| "Inconnu".to_string())
|
||||
)
|
||||
} else {
|
||||
format!("- {}", mention_user(member.user.id))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let ratio = (boosters.len() as f64 / members.len() as f64) * 100.0;
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title("Membres boostant le serveur")
|
||||
.description(format!(
|
||||
"Serveur: **{}**\nBoosters: **{}** / Membres: **{}** ({:.1}%)",
|
||||
guild.name,
|
||||
boosters.len(),
|
||||
members.len(),
|
||||
ratio
|
||||
))
|
||||
.color(0x5865F2);
|
||||
|
||||
if let Some(last_boost) = boosters
|
||||
.iter()
|
||||
.filter_map(|member| {
|
||||
member
|
||||
.premium_since
|
||||
.map(|since| (mention_user(member.user.id), since))
|
||||
})
|
||||
.max_by_key(|(_, since)| *since)
|
||||
{
|
||||
embed = embed.field(
|
||||
"Dernier boost",
|
||||
format!("{} ({})", last_boost.0, discord_ts(last_boost.1, "F")),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
embed = add_list_fields(
|
||||
embed,
|
||||
&lines,
|
||||
&format!(
|
||||
"Liste ({} affichés / {} total)",
|
||||
visible.len(),
|
||||
boosters.len()
|
||||
),
|
||||
);
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub struct BoostersCommand;
|
||||
pub static COMMAND_DESCRIPTOR: BoostersCommand = BoostersCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for BoostersCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "boosters",
|
||||
command: "boosters",
|
||||
category: "general",
|
||||
params: "aucun",
|
||||
summary: "Liste les boosters du serveur",
|
||||
description: "Affiche les membres qui boostent actuellement le serveur.",
|
||||
examples: &["+boosters", "+bs", "+help boosters"],
|
||||
alias_source_key: "boosters",
|
||||
default_aliases: &["bst"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::guild::Role;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::commands::common::{
|
||||
add_list_fields, discord_ts, has_flag, mention_user, parse_limit, send_embed,
|
||||
};
|
||||
|
||||
pub async fn handle_botadmins(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let limit = parse_limit(args, 25, 100);
|
||||
let detailed = has_flag(args, &["--details", "-d", "full"]);
|
||||
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Commande invalide")
|
||||
.description("Cette commande doit être utilisée dans un serveur.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let partial_guild = match guild_id.to_partial_guild(&ctx.http).await {
|
||||
Ok(guild) => guild,
|
||||
Err(why) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de récupérer le serveur: {why}"))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let members = match guild_id.members(&ctx.http, None, None).await {
|
||||
Ok(members) => members,
|
||||
Err(why) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description(format!("Impossible de récupérer les membres: {why}"))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut admin_bots = members
|
||||
.iter()
|
||||
.filter(|member| {
|
||||
member.user.bot
|
||||
&& has_admin_permission(member, partial_guild.owner_id, &partial_guild.roles)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
admin_bots.sort_by_key(|member| member.user.name.to_lowercase());
|
||||
|
||||
if admin_bots.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Bots administrateurs")
|
||||
.description("Aucun bot administrateur trouvé.")
|
||||
.color(0xFEE75C);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let visible = admin_bots.iter().take(limit).collect::<Vec<_>>();
|
||||
let lines = visible
|
||||
.iter()
|
||||
.map(|member| {
|
||||
if detailed {
|
||||
format!(
|
||||
"- {} | ID: {} | Roles: {} | Créé: {}",
|
||||
mention_user(member.user.id),
|
||||
member.user.id,
|
||||
member.roles.len(),
|
||||
discord_ts(member.user.created_at(), "F")
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"- {} (ID: {})",
|
||||
mention_user(member.user.id),
|
||||
member.user.id
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let total_bots = members.iter().filter(|member| member.user.bot).count();
|
||||
let ratio = if total_bots == 0 {
|
||||
0.0
|
||||
} else {
|
||||
(admin_bots.len() as f64 / total_bots as f64) * 100.0
|
||||
};
|
||||
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title("Bots administrateurs")
|
||||
.description(format!(
|
||||
"Serveur: **{}**\nBots admin: **{}** / Bots totaux: **{}** ({:.1}%)",
|
||||
partial_guild.name,
|
||||
admin_bots.len(),
|
||||
total_bots,
|
||||
ratio
|
||||
))
|
||||
.color(0x5865F2);
|
||||
|
||||
embed = add_list_fields(
|
||||
embed,
|
||||
&lines,
|
||||
&format!(
|
||||
"Liste ({} affichés / {} total)",
|
||||
visible.len(),
|
||||
admin_bots.len()
|
||||
),
|
||||
);
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
fn has_admin_permission(member: &Member, owner_id: UserId, roles: &HashMap<RoleId, Role>) -> bool {
|
||||
if member.user.id == owner_id {
|
||||
return true;
|
||||
}
|
||||
|
||||
member.roles.iter().any(|role_id| {
|
||||
roles
|
||||
.get(role_id)
|
||||
.is_some_and(|role| role.permissions.contains(Permissions::ADMINISTRATOR))
|
||||
})
|
||||
}
|
||||
|
||||
pub struct BotadminsCommand;
|
||||
pub static COMMAND_DESCRIPTOR: BotadminsCommand = BotadminsCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for BotadminsCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "botadmins",
|
||||
command: "botadmins",
|
||||
category: "general",
|
||||
params: "aucun",
|
||||
summary: "Liste les admins du bot",
|
||||
description: "Affiche les utilisateurs ayant des droits admin sur le bot.",
|
||||
examples: &["+botadmins", "+bs", "+help botadmins"],
|
||||
alias_source_key: "botadmins",
|
||||
default_aliases: &["bad"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::activity::{RotatingActivityKind, parse_status, start_rotation, stop_rotation};
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::db::{DbPoolKey, set_bot_status};
|
||||
|
||||
pub fn parse_color(value: &str) -> Option<u32> {
|
||||
let v = value.trim().to_lowercase();
|
||||
match v.as_str() {
|
||||
"red" | "rouge" => Some(0xED4245),
|
||||
"green" | "vert" => Some(0x57F287),
|
||||
"blue" | "bleu" => Some(0x5865F2),
|
||||
"yellow" | "jaune" => Some(0xFEE75C),
|
||||
"orange" => Some(0xFAA61A),
|
||||
"purple" | "violet" => Some(0x9B59B6),
|
||||
"pink" | "rose" => Some(0xEB459E),
|
||||
"white" | "blanc" => Some(0xFFFFFF),
|
||||
"black" | "noir" => Some(0x000000),
|
||||
_ => {
|
||||
let hex = v.trim_start_matches('#').trim_start_matches("0x");
|
||||
u32::from_str_radix(hex, 16).ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn save_status_if_db(ctx: &Context, status: &str) {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let pool = {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
};
|
||||
|
||||
if let Some(pool) = pool {
|
||||
let _ = set_bot_status(&pool, bot_id, status).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_activity(ctx: &Context, msg: &Message, command: &str, args: &[&str]) {
|
||||
if args.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+playto|+listen|+watch|+compet|+stream <message>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(kind) = RotatingActivityKind::from_command(command) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let joined = args.join(" ");
|
||||
let messages: Vec<String> = joined
|
||||
.split(",,")
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
if messages.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Aucun message d'activité valide.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
|
||||
let status = {
|
||||
let pool = {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
};
|
||||
|
||||
if let Some(pool) = pool {
|
||||
if let Ok(Some(saved)) = crate::db::get_bot_status(&pool, bot_id).await {
|
||||
parse_status(&saved)
|
||||
} else {
|
||||
OnlineStatus::Online
|
||||
}
|
||||
} else {
|
||||
OnlineStatus::Online
|
||||
}
|
||||
};
|
||||
|
||||
start_rotation(ctx, kind, messages.clone(), status).await;
|
||||
|
||||
let pool = {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
};
|
||||
|
||||
if let Some(pool) = pool {
|
||||
let _ =
|
||||
crate::db::set_bot_activity(&pool, bot_id, kind.as_db(), &messages.join("\n")).await;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Activité mise à jour")
|
||||
.description(format!("{} message(s) configuré(s).", messages.len()))
|
||||
.field(
|
||||
"Rotation",
|
||||
"Les textes alternent toutes les 30 secondes.",
|
||||
false,
|
||||
)
|
||||
.color(0x57F287);
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_remove_activity(ctx: &Context, msg: &Message) {
|
||||
stop_rotation(ctx).await;
|
||||
ctx.set_activity(None);
|
||||
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let pool = {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
};
|
||||
|
||||
if let Some(pool) = pool {
|
||||
let _ = crate::db::clear_bot_activity(&pool, bot_id).await;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Activité supprimée")
|
||||
.description("L'activité du bot a été retirée.")
|
||||
.color(0x57F287);
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_status(ctx: &Context, msg: &Message, command: &str) {
|
||||
let status_name = match command {
|
||||
"+online" => {
|
||||
ctx.online();
|
||||
"online"
|
||||
}
|
||||
"+idle" => {
|
||||
ctx.idle();
|
||||
"idle"
|
||||
}
|
||||
"+dnd" => {
|
||||
ctx.dnd();
|
||||
"dnd"
|
||||
}
|
||||
"+invisible" => {
|
||||
ctx.invisible();
|
||||
"invisible"
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
save_status_if_db(ctx, status_name).await;
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Statut mis à jour")
|
||||
.description(format!("Nouveau statut: {}", status_name))
|
||||
.color(0x57F287);
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::activity::{RotatingActivityKind, parse_status, start_rotation};
|
||||
use crate::db::DbPoolKey;
|
||||
|
||||
pub async fn restore_presence_from_db(ctx: &Context) {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
|
||||
let pool = {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
};
|
||||
|
||||
let Some(pool) = pool else {
|
||||
return;
|
||||
};
|
||||
|
||||
let status = match crate::db::get_bot_status(&pool, bot_id).await {
|
||||
Ok(Some(saved)) => parse_status(&saved),
|
||||
_ => OnlineStatus::Online,
|
||||
};
|
||||
|
||||
ctx.set_presence(None, status);
|
||||
|
||||
let activity_row = crate::db::get_bot_activity(&pool, bot_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
if let Some((kind_raw, messages_raw)) = activity_row {
|
||||
let Some(kind) = RotatingActivityKind::from_db(&kind_raw) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let messages: Vec<String> = messages_raw
|
||||
.split('\n')
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
if !messages.is_empty() {
|
||||
start_rotation(ctx, kind, messages, status).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{send_embed, theme_color};
|
||||
|
||||
fn parse_linear_expression(expr: &str) -> Option<(f64, f64)> {
|
||||
let normalized = expr.replace(' ', "").replace('-', "+-");
|
||||
let mut a = 0.0f64;
|
||||
let mut b = 0.0f64;
|
||||
|
||||
for raw in normalized.split('+') {
|
||||
let term = raw.trim();
|
||||
if term.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if term.contains('x') {
|
||||
let coeff = term.replace('x', "");
|
||||
let c = if coeff.is_empty() || coeff == "+" {
|
||||
1.0
|
||||
} else if coeff == "-" {
|
||||
-1.0
|
||||
} else {
|
||||
coeff.parse::<f64>().ok()?
|
||||
};
|
||||
a += c;
|
||||
} else {
|
||||
b += term.parse::<f64>().ok()?;
|
||||
}
|
||||
}
|
||||
|
||||
Some((a, b))
|
||||
}
|
||||
|
||||
fn solve_linear_equation(input: &str) -> Option<String> {
|
||||
let parts: Vec<&str> = input.split('=').collect();
|
||||
if parts.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (a1, b1) = parse_linear_expression(parts[0])?;
|
||||
let (a2, b2) = parse_linear_expression(parts[1])?;
|
||||
|
||||
let a = a1 - a2;
|
||||
let b = b2 - b1;
|
||||
|
||||
if a.abs() < f64::EPSILON {
|
||||
if b.abs() < f64::EPSILON {
|
||||
return Some("Équation indéterminée (infinité de solutions).".to_string());
|
||||
}
|
||||
return Some("Équation impossible (aucune solution).".to_string());
|
||||
}
|
||||
|
||||
let x = b / a;
|
||||
Some(format!("x = {}", x))
|
||||
}
|
||||
|
||||
pub async fn handle_calc(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let color = theme_color(ctx).await;
|
||||
if args.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+calc <calcul>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let query = args.join(" ");
|
||||
|
||||
let result = if query.contains('=') && query.contains('x') {
|
||||
solve_linear_equation(&query)
|
||||
.unwrap_or_else(|| "Impossible de résoudre cette équation.".to_string())
|
||||
} else {
|
||||
match meval::eval_str(&query) {
|
||||
Ok(value) => value.to_string(),
|
||||
Err(_) => "Expression invalide.".to_string(),
|
||||
}
|
||||
};
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Calcul")
|
||||
.field("Entrée", query, false)
|
||||
.field("Résultat", result, false)
|
||||
.color(color);
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub struct CalcCommand;
|
||||
pub static COMMAND_DESCRIPTOR: CalcCommand = CalcCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for CalcCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "calc",
|
||||
command: "calc",
|
||||
category: "general",
|
||||
params: "<expression>",
|
||||
summary: "Calcule une expression",
|
||||
description: "Evalue une expression numerique simple et renvoie le resultat.",
|
||||
examples: &["+calc", "+cc", "+help calc"],
|
||||
alias_source_key: "calc",
|
||||
default_aliases: &["clc"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::perms_service;
|
||||
|
||||
pub async fn handle_change(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
perms_service::handle_change(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct ChangeCommand;
|
||||
pub static COMMAND_DESCRIPTOR: ChangeCommand = ChangeCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for ChangeCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "change",
|
||||
command: "change",
|
||||
category: "permissions",
|
||||
params: "<commande> <niveau 0-9> | reset",
|
||||
summary: "Change un niveau de permission",
|
||||
description: "Definit le niveau ACL requis pour une commande ou reinitialise les overrides.",
|
||||
examples: &["+change", "+ce", "+help change"],
|
||||
alias_source_key: "change",
|
||||
default_aliases: &["chg"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::perms_service;
|
||||
|
||||
pub async fn handle_changeall(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
perms_service::handle_changeall(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct ChangeallCommand;
|
||||
pub static COMMAND_DESCRIPTOR: ChangeallCommand = ChangeallCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for ChangeallCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "changeall",
|
||||
command: "changeall",
|
||||
category: "permissions",
|
||||
params: "<niveau_source 0-9> <niveau_cible 0-9>",
|
||||
summary: "Change des permissions en masse",
|
||||
description: "Remplace en masse un niveau ACL source par un niveau ACL cible.",
|
||||
examples: &["+changeall", "+cl", "+help changeall"],
|
||||
alias_source_key: "changeall",
|
||||
default_aliases: &["cga"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::parse_channel_id;
|
||||
use crate::commands::common::{discord_ts, send_embed};
|
||||
|
||||
pub async fn handle_channel(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(guild) = guild_id.to_partial_guild(&ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let channels = guild.channels(ctx).await.unwrap_or_default();
|
||||
|
||||
let channel_id = if !args.is_empty() {
|
||||
let search = args.join(" ").to_lowercase();
|
||||
parse_channel_id(args[0]).or_else(|| {
|
||||
// Chercher par nom de canal
|
||||
channels
|
||||
.iter()
|
||||
.find(|(_, c)| c.name.to_lowercase().contains(&search))
|
||||
.map(|(id, _)| *id)
|
||||
})
|
||||
} else {
|
||||
Some(msg.channel_id)
|
||||
};
|
||||
|
||||
let Some(channel_id) = channel_id else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Impossible de parser le canal.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(channel) = channel_id.to_channel(&ctx).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Canal non trouvé.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
match channel {
|
||||
Channel::Guild(gc) => {
|
||||
let created_at = discord_ts(gc.id.created_at(), "F");
|
||||
let channel_type = match gc.kind {
|
||||
ChannelType::Text => "Texte",
|
||||
ChannelType::Voice => "Vocal",
|
||||
ChannelType::Private => "Privé",
|
||||
ChannelType::Category => "Catégorie",
|
||||
ChannelType::News => "Annonces",
|
||||
ChannelType::Stage => "Stage",
|
||||
ChannelType::Directory => "Répertoire",
|
||||
ChannelType::Forum => "Forum",
|
||||
ChannelType::Unknown(_) => "Inconnu",
|
||||
_ => "Autre",
|
||||
};
|
||||
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title(&gc.name)
|
||||
.description(format!("ID: `{}`", gc.id.get()))
|
||||
.color(0x5865F2)
|
||||
.field("Type", channel_type, true)
|
||||
.field("Créé", created_at, true);
|
||||
|
||||
if let Some(topic) = &gc.topic {
|
||||
embed = embed.field("Sujet", topic, false);
|
||||
}
|
||||
|
||||
if let Some(bitrate) = gc.bitrate {
|
||||
embed = embed.field("Bitrate", format!("{} kbps", bitrate / 1000), true);
|
||||
}
|
||||
|
||||
embed = embed.field("NSFW", if gc.nsfw { "Oui" } else { "Non" }, true);
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
_ => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Ce type de canal n'est pas supporté.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChannelCommand;
|
||||
pub static COMMAND_DESCRIPTOR: ChannelCommand = ChannelCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for ChannelCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "channel",
|
||||
command: "channel",
|
||||
category: "general",
|
||||
params: "<#salon/ID>",
|
||||
summary: "Affiche les details dun salon",
|
||||
description: "Affiche les informations utiles dun salon texte ou vocal cible.",
|
||||
examples: &["+channel", "+cl", "+help channel"],
|
||||
alias_source_key: "channel",
|
||||
default_aliases: &["chl"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::advanced_tools;
|
||||
|
||||
pub async fn handle_choose(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
advanced_tools::handle_choose(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct ChooseCommand;
|
||||
pub static COMMAND_DESCRIPTOR: ChooseCommand = ChooseCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for ChooseCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "choose",
|
||||
command: "choose",
|
||||
category: "general",
|
||||
params: "<option1 | option2 | ...>",
|
||||
summary: "Tire une option au hasard",
|
||||
description: "Lance un tirage au sort instantane parmi les options donnees.",
|
||||
examples: &["+choose rouge | bleu | vert"],
|
||||
alias_source_key: "choose",
|
||||
default_aliases: &["pick", "random"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::perms_service;
|
||||
|
||||
pub async fn handle_clear_perms(ctx: &Context, msg: &Message) {
|
||||
perms_service::handle_clear_perms(ctx, msg).await;
|
||||
}
|
||||
|
||||
pub struct ClearPermsCommand;
|
||||
pub static COMMAND_DESCRIPTOR: ClearPermsCommand = ClearPermsCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for ClearPermsCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "clear_perms",
|
||||
command: "clear perms",
|
||||
category: "permissions",
|
||||
params: "aucun",
|
||||
summary: "Vide toutes les permissions scope",
|
||||
description: "Supprime toutes les permissions ACL configurees en base.",
|
||||
examples: &["+clear perms", "+cs", "+help clear perms"],
|
||||
alias_source_key: "clear_perms",
|
||||
default_aliases: &["cpm"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CommandMetadata {
|
||||
pub key: &'static str,
|
||||
pub command: &'static str,
|
||||
pub category: &'static str,
|
||||
pub params: &'static str,
|
||||
pub summary: &'static str,
|
||||
pub description: &'static str,
|
||||
pub examples: &'static [&'static str],
|
||||
pub alias_source_key: &'static str,
|
||||
pub default_aliases: &'static [&'static str],
|
||||
}
|
||||
|
||||
pub trait CommandSpec: Send + Sync {
|
||||
fn metadata(&self) -> CommandMetadata;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
use serenity::builder::{CreateEmbed, CreateMessage};
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::db::{DbPoolKey, get_bot_theme};
|
||||
|
||||
pub fn parse_limit(args: &[&str], default: usize, max: usize) -> usize {
|
||||
args.iter()
|
||||
.find_map(|arg| arg.parse::<usize>().ok())
|
||||
.map(|value| value.clamp(1, max))
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
pub fn has_flag(args: &[&str], names: &[&str]) -> bool {
|
||||
args.iter()
|
||||
.any(|arg| names.iter().any(|name| arg.eq_ignore_ascii_case(name)))
|
||||
}
|
||||
|
||||
pub fn truncate_text(input: &str, max_len: usize) -> String {
|
||||
if input.chars().count() <= max_len {
|
||||
return input.to_string();
|
||||
}
|
||||
|
||||
let mut out = input
|
||||
.chars()
|
||||
.take(max_len.saturating_sub(1))
|
||||
.collect::<String>();
|
||||
out.push('…');
|
||||
out
|
||||
}
|
||||
|
||||
pub fn add_list_fields(mut embed: CreateEmbed, lines: &[String], base_name: &str) -> CreateEmbed {
|
||||
if lines.is_empty() {
|
||||
return embed.field(base_name, "Aucun résultat.", false);
|
||||
}
|
||||
|
||||
let max_fields = 3;
|
||||
let chunk_size = 12;
|
||||
|
||||
for (index, chunk) in lines.chunks(chunk_size).take(max_fields).enumerate() {
|
||||
let field_name = if index == 0 {
|
||||
base_name.to_string()
|
||||
} else {
|
||||
format!("{} (suite {})", base_name, index + 1)
|
||||
};
|
||||
|
||||
let value = truncate_text(&chunk.join("\n"), 1024);
|
||||
embed = embed.field(field_name, value, false);
|
||||
}
|
||||
|
||||
let shown = (chunk_size * max_fields).min(lines.len());
|
||||
if lines.len() > shown {
|
||||
embed = embed.field(
|
||||
"Affichage",
|
||||
format!("{} éléments affichés sur {}.", shown, lines.len()),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
embed
|
||||
}
|
||||
|
||||
pub fn mention_user(user_id: UserId) -> String {
|
||||
format!("<@{}>", user_id.get())
|
||||
}
|
||||
|
||||
pub fn discord_ts(timestamp: Timestamp, style: &str) -> String {
|
||||
format!("<t:{}:{}>", timestamp.unix_timestamp(), style)
|
||||
}
|
||||
|
||||
pub async fn send_embed(ctx: &Context, msg: &Message, embed: CreateEmbed) {
|
||||
let color = theme_color(ctx).await;
|
||||
let embed = embed.color(color);
|
||||
|
||||
let _ = msg
|
||||
.channel_id
|
||||
.send_message(&ctx.http, CreateMessage::new().embed(embed))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn theme_color(ctx: &Context) -> u32 {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let pool = {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
};
|
||||
|
||||
if let Some(pool) = pool {
|
||||
if let Ok(Some(color)) = get_bot_theme(&pool, bot_id).await {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
0xFF0000
|
||||
}
|
||||
|
||||
pub fn parse_role(guild: &PartialGuild, input: &str) -> Option<Role> {
|
||||
// Essayer de parser comme mention <@&id>
|
||||
if let Ok(id) = input
|
||||
.trim_start_matches("<@&")
|
||||
.trim_end_matches('>')
|
||||
.parse::<u64>()
|
||||
{
|
||||
if let Some(role) = guild.roles.get(&RoleId::new(id)) {
|
||||
return Some(role.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Essayer de parser comme ID brut
|
||||
if let Ok(id) = input.parse::<u64>() {
|
||||
if let Some(role) = guild.roles.get(&RoleId::new(id)) {
|
||||
return Some(role.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Chercher par nom (case-insensitive)
|
||||
let search = input.to_lowercase();
|
||||
guild
|
||||
.roles
|
||||
.values()
|
||||
.find(|r| r.name.to_lowercase().contains(&search))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn parse_channel_id(input: &str) -> Option<ChannelId> {
|
||||
// Essayer de parser comme mention <#id>
|
||||
if let Ok(id) = input
|
||||
.trim_start_matches("<#")
|
||||
.trim_end_matches('>')
|
||||
.parse::<u64>()
|
||||
{
|
||||
return Some(ChannelId::new(id));
|
||||
}
|
||||
|
||||
// Essayer de parser comme ID brut
|
||||
if let Ok(id) = input.parse::<u64>() {
|
||||
return Some(ChannelId::new(id));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::botconfig_common;
|
||||
|
||||
pub async fn handle_compet(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
botconfig_common::handle_activity(ctx, msg, "+compet", args).await;
|
||||
}
|
||||
|
||||
pub struct CompetCommand;
|
||||
pub static COMMAND_DESCRIPTOR: CompetCommand = CompetCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for CompetCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "compet",
|
||||
command: "compet",
|
||||
category: "profile",
|
||||
params: "<texte[, ,texte2,...]>",
|
||||
summary: "Definit une activite competing",
|
||||
description: "Configure la rotation des messages d activite en mode competing.",
|
||||
examples: &["+compet", "+ct", "+help compet"],
|
||||
alias_source_key: "compet",
|
||||
default_aliases: &["cpt"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::perms_service;
|
||||
|
||||
pub async fn handle_del(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
perms_service::handle_del_perm(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct DelCommand;
|
||||
pub static COMMAND_DESCRIPTOR: DelCommand = DelCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for DelCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "del",
|
||||
command: "del",
|
||||
category: "permissions",
|
||||
params: "perm <@&rôle/@membre/ID>",
|
||||
summary: "Supprime des permissions scope",
|
||||
description: "Supprime les permissions ACL associees a un role ou utilisateur.",
|
||||
examples: &["+del", "+dl", "+help del"],
|
||||
alias_source_key: "del",
|
||||
default_aliases: &["dlp"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
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_discussion(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if args.len() < 2 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+discussion <ID/nombre> <message>`")
|
||||
.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 content = args[1..].join(" ");
|
||||
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;
|
||||
};
|
||||
|
||||
for channel in channels.values() {
|
||||
if matches!(channel.kind, ChannelType::Text | ChannelType::News) {
|
||||
let _ = channel
|
||||
.say(
|
||||
&ctx.http,
|
||||
format!("[Discussion via {}] {}", msg.author.tag(), content),
|
||||
)
|
||||
.await;
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Discussion envoyée")
|
||||
.description(format!("Message transmis dans `{}`.", guild_id.get()))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Aucun salon texte trouvable sur ce serveur.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
pub struct DiscussionCommand;
|
||||
pub static COMMAND_DESCRIPTOR: DiscussionCommand = DiscussionCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for DiscussionCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "discussion",
|
||||
command: "discussion",
|
||||
category: "profile",
|
||||
params: "<ID_serveur/index> <message...>",
|
||||
summary: "Diffuse un message serveur",
|
||||
description: "Envoie un message de discussion sur un serveur cible.",
|
||||
examples: &["+discussion", "+dn", "+help discussion"],
|
||||
alias_source_key: "discussion",
|
||||
default_aliases: &["dsc"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::botconfig_common;
|
||||
|
||||
pub async fn handle_dnd(ctx: &Context, msg: &Message) {
|
||||
botconfig_common::handle_status(ctx, msg, "+dnd").await;
|
||||
}
|
||||
|
||||
pub struct DndCommand;
|
||||
pub static COMMAND_DESCRIPTOR: DndCommand = DndCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for DndCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "dnd",
|
||||
command: "dnd",
|
||||
category: "profile",
|
||||
params: "aucun",
|
||||
summary: "Passe le bot en dnd",
|
||||
description: "Change le statut du bot en do not disturb et sauvegarde ce statut.",
|
||||
examples: &["+dnd", "+dd", "+help dnd"],
|
||||
alias_source_key: "dnd",
|
||||
default_aliases: &["dnm"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{send_embed, theme_color};
|
||||
|
||||
fn parse_custom_emoji(input: &str) -> Option<(bool, String)> {
|
||||
if !(input.starts_with("<:") || input.starts_with("<a:")) || !input.ends_with('>') {
|
||||
return None;
|
||||
}
|
||||
|
||||
let animated = input.starts_with("<a:");
|
||||
let inner = input.trim_start_matches('<').trim_end_matches('>');
|
||||
let parts: Vec<&str> = inner.split(':').collect();
|
||||
if parts.len() != 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let id = parts[2].to_string();
|
||||
Some((animated, id))
|
||||
}
|
||||
|
||||
fn unicode_emoji_url(input: &str) -> Option<String> {
|
||||
if input.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let codepoints = input
|
||||
.chars()
|
||||
.filter(|c| *c as u32 != 0xFE0F)
|
||||
.map(|c| format!("{:x}", c as u32))
|
||||
.collect::<Vec<_>>()
|
||||
.join("-");
|
||||
|
||||
if codepoints.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(format!(
|
||||
"https://twemoji.maxcdn.com/v/latest/72x72/{}.png",
|
||||
codepoints
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn handle_emoji(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let color = theme_color(ctx).await;
|
||||
if args.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+emoji <émoji>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let input = args.join(" ");
|
||||
|
||||
let url = if let Some((animated, id)) = parse_custom_emoji(&input) {
|
||||
let ext = if animated { "gif" } else { "png" };
|
||||
format!("https://cdn.discordapp.com/emojis/{}.{}?size=1024", id, ext)
|
||||
} else if let Some(url) = unicode_emoji_url(input.trim()) {
|
||||
url
|
||||
} else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Émoji invalide. Utilise un émoji Unicode ou un émoji custom Discord.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Image de l'émoji")
|
||||
.image(url)
|
||||
.color(color);
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub struct EmojiCommand;
|
||||
pub static COMMAND_DESCRIPTOR: EmojiCommand = EmojiCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for EmojiCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "emoji",
|
||||
command: "emoji",
|
||||
category: "general",
|
||||
params: "<emoji>",
|
||||
summary: "Affiche les infos dun emoji",
|
||||
description: "Affiche les details dun emoji fourni.",
|
||||
examples: &["+emoji", "+ei", "+help emoji"],
|
||||
alias_source_key: "emoji",
|
||||
default_aliases: &["emj"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,799 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serenity::builder::{
|
||||
CreateActionRow, CreateButton, CreateCommand, CreateEmbed, CreateInteractionResponse,
|
||||
CreateInteractionResponseMessage, CreateMessage, CreateSelectMenu, CreateSelectMenuKind,
|
||||
CreateSelectMenuOption,
|
||||
};
|
||||
use serenity::model::application::{
|
||||
Command, CommandInteraction, ComponentInteractionDataKind, Interaction,
|
||||
};
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::alias::resolve_alias;
|
||||
use crate::commands::common::{add_list_fields, truncate_text};
|
||||
use crate::db::{
|
||||
DbPoolKey, get_help_aliases_enabled, get_help_perms_enabled, get_help_type,
|
||||
list_command_aliases,
|
||||
};
|
||||
use crate::permissions;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum HelpLayout {
|
||||
Button,
|
||||
Select,
|
||||
Hybrid,
|
||||
}
|
||||
|
||||
impl HelpLayout {
|
||||
fn from_str(value: &str) -> Self {
|
||||
match value.to_lowercase().as_str() {
|
||||
"select" => Self::Select,
|
||||
"hybrid" => Self::Hybrid,
|
||||
_ => Self::Button,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Button => "button",
|
||||
Self::Select => "select",
|
||||
Self::Hybrid => "hybrid",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HelpPage {
|
||||
key: &'static str,
|
||||
title: &'static str,
|
||||
description: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct CommandDoc {
|
||||
key: &'static str,
|
||||
command: &'static str,
|
||||
params: &'static str,
|
||||
summary: &'static str,
|
||||
description: &'static str,
|
||||
examples: &'static [&'static str],
|
||||
alias_source_key: Option<&'static str>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct HelpState {
|
||||
layout: HelpLayout,
|
||||
aliases_enabled: bool,
|
||||
perms_enabled: bool,
|
||||
}
|
||||
|
||||
const HELP_FALLBACK_EXAMPLES: &[&str] = &["+help", "+help ping"];
|
||||
const HELP_PAGES: &[HelpPage] = &[
|
||||
HelpPage {
|
||||
key: "infos",
|
||||
title: "Infos",
|
||||
description: "Informations sur le serveur, les membres et les profils.",
|
||||
},
|
||||
HelpPage {
|
||||
key: "logs",
|
||||
title: "Logs",
|
||||
description: "Configuration des logs de modération, messages, vocal et boosts.",
|
||||
},
|
||||
HelpPage {
|
||||
key: "moderation",
|
||||
title: "Modération",
|
||||
description: "Sanctions, nettoyage et commandes de modération générale.",
|
||||
},
|
||||
HelpPage {
|
||||
key: "roles",
|
||||
title: "Rôles",
|
||||
description: "Gestion des rôles individuels, temporaires et massifs.",
|
||||
},
|
||||
HelpPage {
|
||||
key: "salons_vocal",
|
||||
title: "Salons & Vocal",
|
||||
description: "Gestion des salons texte et commandes de déplacement vocal.",
|
||||
},
|
||||
HelpPage {
|
||||
key: "outils",
|
||||
title: "Outils",
|
||||
description: "Giveaways, utilitaires, embeds et automatisations de contenu.",
|
||||
},
|
||||
HelpPage {
|
||||
key: "bot",
|
||||
title: "Bot & Présence",
|
||||
description: "Configuration du bot, thème, activité et présence.",
|
||||
},
|
||||
HelpPage {
|
||||
key: "administration",
|
||||
title: "Administration",
|
||||
description: "Owners, blacklist, préfixes, MP et configuration globale.",
|
||||
},
|
||||
HelpPage {
|
||||
key: "permissions",
|
||||
title: "Permissions & Aide",
|
||||
description: "Permissions, alias et configuration de l'interface d'aide.",
|
||||
},
|
||||
];
|
||||
|
||||
fn help_page_for_command(
|
||||
meta: &crate::commands::command_contract::CommandMetadata,
|
||||
) -> &'static str {
|
||||
match meta.key {
|
||||
"modlog" | "messagelog" | "voicelog" | "boostlog" | "rolelog" | "raidlog"
|
||||
| "autoconfiglog" | "nolog" | "join" | "boostembed" | "set_modlogs" | "set_boostembed"
|
||||
| "leave_settings" | "viewlogs" => "logs",
|
||||
"warn"
|
||||
| "mute"
|
||||
| "tempmute"
|
||||
| "unmute"
|
||||
| "cmute"
|
||||
| "tempcmute"
|
||||
| "uncmute"
|
||||
| "mutelist"
|
||||
| "unmuteall"
|
||||
| "kick"
|
||||
| "ban"
|
||||
| "tempban"
|
||||
| "unban"
|
||||
| "banlist"
|
||||
| "unbanall"
|
||||
| "sanctions"
|
||||
| "del_sanction"
|
||||
| "clear_sanctions"
|
||||
| "clear_all_sanctions"
|
||||
| "cleanup"
|
||||
| "renew"
|
||||
| "clear_messages" => "moderation",
|
||||
"addrole" | "delrole" | "derank" | "massiverole" | "unmassiverole" | "temprole"
|
||||
| "untemprole" | "sync" => "roles",
|
||||
"lock" | "unlock" | "lockall" | "unlockall" | "hide" | "unhide" | "hideall"
|
||||
| "unhideall" | "voicemove" | "voicekick" | "bringall" => "salons_vocal",
|
||||
"giveaway" | "end" | "reroll" | "choose" | "calc" | "emoji" | "embed" | "say"
|
||||
| "create" | "newsticker" | "button" | "autoreact" | "snipe" | "loading" | "backup"
|
||||
| "autobackup" => "outils",
|
||||
"shadowbot" | "set" | "theme" | "playto" | "listen" | "watch" | "compet" | "stream"
|
||||
| "remove_activity" | "online" | "idle" | "dnd" | "invisible" | "change" | "changeall" => {
|
||||
"bot"
|
||||
}
|
||||
"owner" | "unowner" | "clear_owners" | "bl" | "unbl" | "blinfo" | "clear_bl"
|
||||
| "allbots" | "alladmins" | "botadmins" | "mainprefix" | "prefix" | "mp" | "invite"
|
||||
| "leave" | "discussion" => "administration",
|
||||
"perms" | "del" | "clear_perms" | "allperms" | "alias" | "help" | "helptype"
|
||||
| "helpalias" => "permissions",
|
||||
_ => match meta.category {
|
||||
"general" => "infos",
|
||||
"profile" => "bot",
|
||||
"admin" => "administration",
|
||||
"permissions" => "permissions",
|
||||
_ => "infos",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn help_page_title(key: &str) -> &'static str {
|
||||
HELP_PAGES
|
||||
.iter()
|
||||
.find(|page| page.key == key)
|
||||
.map(|page| page.title)
|
||||
.unwrap_or("Infos")
|
||||
}
|
||||
|
||||
fn help_page_title_for_command_key(key: &str) -> &'static str {
|
||||
crate::commands::command_metadata_by_key(key)
|
||||
.map(|meta| help_page_title(help_page_for_command(&meta)))
|
||||
.unwrap_or("Infos")
|
||||
}
|
||||
|
||||
fn help_metadata_lookup_key(input: &str) -> Option<&'static str> {
|
||||
let normalized = help_lookup_key(input);
|
||||
let underscored = normalized.replace(' ', "_");
|
||||
|
||||
crate::commands::all_command_metadata()
|
||||
.into_iter()
|
||||
.find(|meta| {
|
||||
meta.key.eq_ignore_ascii_case(&normalized)
|
||||
|| meta.key.eq_ignore_ascii_case(&underscored)
|
||||
|| meta.command.eq_ignore_ascii_case(&normalized)
|
||||
})
|
||||
.map(|meta| meta.key)
|
||||
}
|
||||
|
||||
fn help_page_matches_input(page: &HelpPage, input: &str) -> bool {
|
||||
let normalized = help_lookup_key(input);
|
||||
let aliases = match page.key {
|
||||
"infos" => &["general", "info", "informations"][..],
|
||||
"logs" => &["log", "journal"][..],
|
||||
"moderation" => &["mod", "sanction"][..],
|
||||
"roles" => &["role", "roles"][..],
|
||||
"salons_vocal" => &["salon", "salons", "vocal", "voice", "channels"][..],
|
||||
"outils" => &["utilitaires", "tools", "giveaway"][..],
|
||||
"bot" => &["profil", "presence", "activite", "activity"][..],
|
||||
"administration" => &["admin", "admins"][..],
|
||||
"permissions" => &["permission", "perms", "aide", "help"][..],
|
||||
_ => &[][..],
|
||||
};
|
||||
|
||||
page.key.eq_ignore_ascii_case(&normalized)
|
||||
|| help_lookup_key(page.title).eq_ignore_ascii_case(&normalized)
|
||||
|| aliases
|
||||
.iter()
|
||||
.any(|alias| alias.eq_ignore_ascii_case(&normalized))
|
||||
}
|
||||
|
||||
async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
}
|
||||
|
||||
async fn current_help_state(ctx: &Context) -> HelpState {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let pool = pool(ctx).await;
|
||||
|
||||
let layout = if let Some(pool) = &pool {
|
||||
get_help_type(pool, bot_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|value| HelpLayout::from_str(&value))
|
||||
.unwrap_or(HelpLayout::Button)
|
||||
} else {
|
||||
HelpLayout::Button
|
||||
};
|
||||
|
||||
let aliases_enabled = if let Some(pool) = &pool {
|
||||
get_help_aliases_enabled(pool, bot_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(true)
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
let perms_enabled = if let Some(pool) = &pool {
|
||||
get_help_perms_enabled(pool, bot_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(true)
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
HelpState {
|
||||
layout,
|
||||
aliases_enabled,
|
||||
perms_enabled,
|
||||
}
|
||||
}
|
||||
|
||||
async fn aliases_map(ctx: &Context) -> BTreeMap<String, Vec<String>> {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let mut out: BTreeMap<String, Vec<String>> = BTreeMap::new();
|
||||
|
||||
for meta in crate::commands::all_command_metadata() {
|
||||
if !meta.default_aliases.is_empty() {
|
||||
out.entry(meta.alias_source_key.to_string())
|
||||
.or_default()
|
||||
.extend(meta.default_aliases.iter().map(|alias| alias.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pool) = pool(ctx).await {
|
||||
if let Ok(rows) = list_command_aliases(&pool, bot_id).await {
|
||||
for (alias, command) in rows {
|
||||
out.entry(command).or_default().push(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for aliases in out.values_mut() {
|
||||
aliases.sort();
|
||||
aliases.dedup();
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn command_doc(key: &str) -> Option<CommandDoc> {
|
||||
let meta = match key {
|
||||
"mp_settings" | "mp_sent" | "mp_delete" => crate::commands::command_metadata_by_key("mp")?,
|
||||
"server_list" => crate::commands::command_metadata_by_key("server")?,
|
||||
"change_reset" => crate::commands::command_metadata_by_key("change")?,
|
||||
"set_perm" => crate::commands::command_metadata_by_key("set")?,
|
||||
"del_perm" => crate::commands::command_metadata_by_key("del")?,
|
||||
other => crate::commands::command_metadata_by_key(other)?,
|
||||
};
|
||||
|
||||
Some(CommandDoc {
|
||||
key: meta.key,
|
||||
command: meta.command,
|
||||
params: meta.params,
|
||||
summary: meta.summary,
|
||||
description: meta.description,
|
||||
examples: if meta.examples.is_empty() {
|
||||
HELP_FALLBACK_EXAMPLES
|
||||
} else {
|
||||
meta.examples
|
||||
},
|
||||
alias_source_key: Some(meta.alias_source_key),
|
||||
})
|
||||
}
|
||||
|
||||
fn help_lookup_key(input: &str) -> String {
|
||||
input
|
||||
.trim()
|
||||
.trim_start_matches('+')
|
||||
.to_lowercase()
|
||||
.replace('_', " ")
|
||||
.split_whitespace()
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
fn help_lookup_to_key(input: &str) -> Option<&'static str> {
|
||||
let matched = match help_lookup_key(input).as_str() {
|
||||
"help" => Some("help"),
|
||||
"ping" => Some("ping"),
|
||||
"allbots" => Some("allbots"),
|
||||
"alladmins" => Some("alladmins"),
|
||||
"botadmins" => Some("botadmins"),
|
||||
"boosters" => Some("boosters"),
|
||||
"rolemembers" => Some("rolemembers"),
|
||||
"serverinfo" => Some("serverinfo"),
|
||||
"vocinfo" => Some("vocinfo"),
|
||||
"role" => Some("role"),
|
||||
"channel" => Some("channel"),
|
||||
"user" => Some("user"),
|
||||
"member" => Some("member"),
|
||||
"pic" => Some("pic"),
|
||||
"banner" => Some("banner"),
|
||||
"server" => Some("server"),
|
||||
"server list" => Some("server_list"),
|
||||
"snipe" => Some("snipe"),
|
||||
"emoji" => Some("emoji"),
|
||||
"giveaway" => Some("giveaway"),
|
||||
"end" | "end giveaway" => Some("end"),
|
||||
"reroll" => Some("reroll"),
|
||||
"choose" => Some("choose"),
|
||||
"embed" => Some("embed"),
|
||||
"backup" | "backup list" | "backup delete" | "backup load" => Some("backup"),
|
||||
"autobackup" => Some("autobackup"),
|
||||
"loading" => Some("loading"),
|
||||
"create" => Some("create"),
|
||||
"newsticker" => Some("newsticker"),
|
||||
"massiverole" => Some("massiverole"),
|
||||
"unmassiverole" => Some("unmassiverole"),
|
||||
"voicemove" => Some("voicemove"),
|
||||
"voicekick" => Some("voicekick"),
|
||||
"cleanup" => Some("cleanup"),
|
||||
"bringall" => Some("bringall"),
|
||||
"renew" => Some("renew"),
|
||||
"unbanall" => Some("unbanall"),
|
||||
"temprole" => Some("temprole"),
|
||||
"untemprole" => Some("untemprole"),
|
||||
"sync" => Some("sync"),
|
||||
"button" => Some("button"),
|
||||
"autoreact" => Some("autoreact"),
|
||||
"calc" => Some("calc"),
|
||||
"shadowbot" => Some("shadowbot"),
|
||||
"set" => Some("set"),
|
||||
"theme" => Some("theme"),
|
||||
"playto" => Some("playto"),
|
||||
"listen" => Some("listen"),
|
||||
"watch" => Some("watch"),
|
||||
"compet" => Some("compet"),
|
||||
"stream" => Some("stream"),
|
||||
"remove activity" => Some("remove_activity"),
|
||||
"online" => Some("online"),
|
||||
"idle" => Some("idle"),
|
||||
"dnd" => Some("dnd"),
|
||||
"invisible" => Some("invisible"),
|
||||
"mp" => Some("mp"),
|
||||
"mp settings" => Some("mp_settings"),
|
||||
"mp sent" => Some("mp_sent"),
|
||||
"mp delete" | "mp del" => Some("mp_delete"),
|
||||
"discussion" => Some("discussion"),
|
||||
"owner" => Some("owner"),
|
||||
"unowner" => Some("unowner"),
|
||||
"clear owners" => Some("clear_owners"),
|
||||
"bl" => Some("bl"),
|
||||
"unbl" => Some("unbl"),
|
||||
"blinfo" => Some("blinfo"),
|
||||
"clear bl" => Some("clear_bl"),
|
||||
"say" => Some("say"),
|
||||
"invite" => Some("invite"),
|
||||
"leave" => Some("leave"),
|
||||
"change" => Some("change"),
|
||||
"change reset" => Some("change_reset"),
|
||||
"changeall" => Some("changeall"),
|
||||
"mainprefix" => Some("mainprefix"),
|
||||
"prefix" => Some("prefix"),
|
||||
"perms" => Some("perms"),
|
||||
"allperms" => Some("allperms"),
|
||||
"set perm" => Some("set_perm"),
|
||||
"del perm" => Some("del_perm"),
|
||||
"clear perms" => Some("clear_perms"),
|
||||
"alias" => Some("alias"),
|
||||
"helpsetting" => Some("helpsetting"),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
matched.or_else(|| help_metadata_lookup_key(input))
|
||||
}
|
||||
|
||||
fn help_page_index(key: &str) -> Option<usize> {
|
||||
HELP_PAGES
|
||||
.iter()
|
||||
.position(|page| help_page_matches_input(page, key))
|
||||
}
|
||||
|
||||
fn help_page_from_input(input: &str) -> Option<usize> {
|
||||
if let Ok(index) = input.parse::<usize>() {
|
||||
if index >= 1 && index <= HELP_PAGES.len() {
|
||||
return Some(index - 1);
|
||||
}
|
||||
}
|
||||
|
||||
help_page_index(input)
|
||||
}
|
||||
|
||||
fn format_permission_level(level: u8) -> String {
|
||||
format!("[{}]", level)
|
||||
}
|
||||
|
||||
fn permission_level_description(level: u8) -> &'static str {
|
||||
match level {
|
||||
0 => "[0] Public",
|
||||
2 => "[2] Accès spécial",
|
||||
8 => "[8] Modérateur+",
|
||||
9 => "[9] Propriétaire",
|
||||
_ => "[?] Inconnu",
|
||||
}
|
||||
}
|
||||
|
||||
fn help_page_content(
|
||||
page: &HelpPage,
|
||||
alias_map: &BTreeMap<String, Vec<String>>,
|
||||
aliases_enabled: bool,
|
||||
perms_enabled: bool,
|
||||
) -> Vec<String> {
|
||||
let mut commands = crate::commands::all_command_metadata()
|
||||
.into_iter()
|
||||
.filter(|meta| help_page_for_command(meta).eq_ignore_ascii_case(page.key))
|
||||
.collect::<Vec<_>>();
|
||||
commands.sort_by(|a, b| a.command.to_lowercase().cmp(&b.command.to_lowercase()));
|
||||
|
||||
let mut lines = Vec::with_capacity(commands.len());
|
||||
|
||||
for meta in commands {
|
||||
let label = meta.command;
|
||||
let summary = meta.summary;
|
||||
let alias_key = meta.alias_source_key;
|
||||
let permission = if perms_enabled {
|
||||
format!(" {}", format_permission_level(permissions::default_permission(meta.key)))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
if aliases_enabled {
|
||||
if let Some(aliases) = alias_map.get(alias_key) {
|
||||
if aliases.is_empty() {
|
||||
lines.push(format!("`+{}`{} - {}", label, permission, summary));
|
||||
} else {
|
||||
lines.push(format!(
|
||||
"`+{}`{} - {} · alias: `{}`",
|
||||
label, permission, summary,
|
||||
aliases.join("`, `")
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(format!("`+{}`{} - {}", label, permission, summary));
|
||||
}
|
||||
|
||||
if lines.is_empty() {
|
||||
lines.push("Aucune commande dans cette catégorie.".to_string());
|
||||
}
|
||||
|
||||
lines
|
||||
}
|
||||
|
||||
fn build_help_embed(
|
||||
page_index: usize,
|
||||
state: &HelpState,
|
||||
alias_map: &BTreeMap<String, Vec<String>>,
|
||||
) -> CreateEmbed {
|
||||
let page = &HELP_PAGES[page_index];
|
||||
let lines = help_page_content(page, alias_map, state.aliases_enabled, state.perms_enabled);
|
||||
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title(format!("Aide · {}", page.title))
|
||||
.description(format!(
|
||||
"Page {}/{} · mode `{}` · aliases {} · perms {}\n{}",
|
||||
page_index + 1,
|
||||
HELP_PAGES.len(),
|
||||
state.layout.as_str(),
|
||||
if state.aliases_enabled {
|
||||
"activés"
|
||||
} else {
|
||||
"désactivés"
|
||||
},
|
||||
if state.perms_enabled {
|
||||
"activées"
|
||||
} else {
|
||||
"désactivées"
|
||||
},
|
||||
page.description,
|
||||
))
|
||||
.color(0x5865F2);
|
||||
|
||||
embed = add_list_fields(embed, &lines, "Commandes");
|
||||
embed
|
||||
}
|
||||
|
||||
fn help_components(owner_id: UserId, page_index: usize, state: &HelpState) -> Vec<CreateActionRow> {
|
||||
let total = HELP_PAGES.len().max(1);
|
||||
let prev_page = page_index.saturating_sub(1);
|
||||
let next_page = (page_index + 1).min(total - 1);
|
||||
let custom_prev = format!("help:nav:{}:{}", owner_id.get(), prev_page);
|
||||
let custom_next = format!("help:nav:{}:{}", owner_id.get(), next_page);
|
||||
|
||||
let mut rows = Vec::new();
|
||||
|
||||
match state.layout {
|
||||
HelpLayout::Button | HelpLayout::Hybrid => {
|
||||
rows.push(CreateActionRow::Buttons(vec![
|
||||
CreateButton::new(custom_prev)
|
||||
.label("◀ Précédent")
|
||||
.style(ButtonStyle::Primary)
|
||||
.disabled(page_index == 0),
|
||||
CreateButton::new(custom_next)
|
||||
.label("Suivant ▶")
|
||||
.style(ButtonStyle::Primary)
|
||||
.disabled(page_index + 1 >= total),
|
||||
]));
|
||||
}
|
||||
HelpLayout::Select => {}
|
||||
}
|
||||
|
||||
match state.layout {
|
||||
HelpLayout::Select | HelpLayout::Hybrid => {
|
||||
let options = HELP_PAGES
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, page)| {
|
||||
CreateSelectMenuOption::new(page.title, index.to_string())
|
||||
.description(truncate_text(page.description, 100))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let menu = CreateSelectMenu::new(
|
||||
format!("help:select:{}", owner_id.get()),
|
||||
CreateSelectMenuKind::String { options },
|
||||
)
|
||||
.placeholder("Choisir une page d'aide");
|
||||
|
||||
rows.push(CreateActionRow::SelectMenu(menu));
|
||||
}
|
||||
HelpLayout::Button => {}
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
|
||||
fn parse_help_component_id(custom_id: &str) -> Option<(&str, u64, Option<usize>)> {
|
||||
let parts = custom_id.split(':').collect::<Vec<_>>();
|
||||
if parts.len() < 3 || parts.first().copied()? != "help" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let kind = parts.get(1).copied()?;
|
||||
let owner_id = parts.get(2)?.parse::<u64>().ok()?;
|
||||
let page = parts.get(3).and_then(|value| value.parse::<usize>().ok());
|
||||
Some((kind, owner_id, page))
|
||||
}
|
||||
|
||||
pub async fn register_slash_help(ctx: &Context) {
|
||||
let _ = Command::create_global_command(
|
||||
&ctx.http,
|
||||
CreateCommand::new("help").description("Affiche l'aide du bot"),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_help_slash(ctx: &Context, command: &CommandInteraction) {
|
||||
let state = current_help_state(ctx).await;
|
||||
let alias_map = aliases_map(ctx).await;
|
||||
let embed = build_help_embed(0, &state, &alias_map);
|
||||
let components = help_components(command.user.id, 0, &state);
|
||||
|
||||
let _ = command
|
||||
.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::Message(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.embed(embed)
|
||||
.components(components),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_slash_interaction(ctx: &Context, interaction: &Interaction) -> bool {
|
||||
let Interaction::Command(command) = interaction else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if command.data.name != "help" {
|
||||
return false;
|
||||
}
|
||||
|
||||
handle_help_slash(ctx, command).await;
|
||||
true
|
||||
}
|
||||
|
||||
pub async fn handle_help_component(ctx: &Context, component: &ComponentInteraction) -> bool {
|
||||
let Some((kind, owner_id, page)) = parse_help_component_id(&component.data.custom_id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if component.user.id.get() != owner_id {
|
||||
let _ = component
|
||||
.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::Message(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.content("Seul l'auteur de l'aide peut utiliser ces contrôles.")
|
||||
.ephemeral(true),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
|
||||
let state = current_help_state(ctx).await;
|
||||
let alias_map = aliases_map(ctx).await;
|
||||
let page_index = match kind {
|
||||
"nav" => page.unwrap_or(0).min(HELP_PAGES.len().saturating_sub(1)),
|
||||
"select" => match &component.data.kind {
|
||||
ComponentInteractionDataKind::StringSelect { values } => values
|
||||
.first()
|
||||
.and_then(|value| value.parse::<usize>().ok())
|
||||
.unwrap_or(0)
|
||||
.min(HELP_PAGES.len().saturating_sub(1)),
|
||||
_ => 0,
|
||||
},
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
let embed = build_help_embed(page_index, &state, &alias_map);
|
||||
let components = help_components(component.user.id, page_index, &state);
|
||||
|
||||
let _ = component
|
||||
.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::UpdateMessage(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.embed(embed)
|
||||
.components(components),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub async fn handle_help(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let state = current_help_state(ctx).await;
|
||||
let alias_map = aliases_map(ctx).await;
|
||||
|
||||
if !args.is_empty() {
|
||||
let joined = args.join(" ");
|
||||
|
||||
let mut resolved_key = help_lookup_to_key(&joined).map(|s| s.to_string());
|
||||
if resolved_key.is_none() {
|
||||
let first = args[0];
|
||||
if let Some(key) = help_lookup_to_key(first) {
|
||||
resolved_key = Some(key.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if resolved_key.is_none() {
|
||||
let alias_input = help_lookup_key(&joined).replace(' ', "_");
|
||||
if let Some(alias_target) = resolve_alias(ctx, &alias_input).await {
|
||||
resolved_key = Some(alias_target);
|
||||
}
|
||||
}
|
||||
|
||||
if resolved_key.is_none() {
|
||||
resolved_key = help_metadata_lookup_key(&joined).map(|key| key.to_string());
|
||||
}
|
||||
|
||||
if let Some(key) = resolved_key {
|
||||
if let Some(doc) = command_doc(&key) {
|
||||
let aliases = doc
|
||||
.alias_source_key
|
||||
.and_then(|alias_key| alias_map.get(alias_key))
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let alias_text = if aliases.is_empty() {
|
||||
"Aucun alias".to_string()
|
||||
} else {
|
||||
aliases
|
||||
.iter()
|
||||
.map(|alias| format!("`{}`", alias))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
};
|
||||
|
||||
let examples = doc
|
||||
.examples
|
||||
.iter()
|
||||
.map(|ex| format!("`{}`", ex))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title(format!("Aide commande · +{}", doc.command))
|
||||
.description(doc.description)
|
||||
.field("Commande", format!("`+{}`", doc.command), false)
|
||||
.field("Clé ACL", format!("`{}`", doc.key), false)
|
||||
.field("Catégorie", help_page_title_for_command_key(doc.key), false)
|
||||
.field("Permission", permission_level_description(permissions::default_permission(doc.key)), false)
|
||||
.field("Alias", alias_text, false)
|
||||
.field("Paramètres", doc.params, false)
|
||||
.field("Résumé", doc.summary, false)
|
||||
.field("Exemples", truncate_text(&examples, 1024), false)
|
||||
.color(crate::commands::common::theme_color(ctx).await);
|
||||
|
||||
let _ = msg
|
||||
.channel_id
|
||||
.send_message(&ctx.http, CreateMessage::new().embed(embed))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let page_index = args
|
||||
.first()
|
||||
.and_then(|input| help_page_from_input(input))
|
||||
.unwrap_or(0)
|
||||
.min(HELP_PAGES.len().saturating_sub(1));
|
||||
|
||||
let embed = build_help_embed(page_index, &state, &alias_map);
|
||||
let components = help_components(msg.author.id, page_index, &state);
|
||||
|
||||
let _ = msg
|
||||
.channel_id
|
||||
.send_message(
|
||||
&ctx.http,
|
||||
CreateMessage::new().embed(embed).components(components),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
pub struct HelpCommand;
|
||||
pub static COMMAND_DESCRIPTOR: HelpCommand = HelpCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for HelpCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "help",
|
||||
command: "help",
|
||||
category: "general",
|
||||
params: "[commande|page]",
|
||||
summary: "Affiche laide des commandes",
|
||||
description: "Affiche les pages daide du bot ou la fiche detaillee dune commande avec parametres, aliases et exemples.",
|
||||
examples: &["+help", "+hp", "+help help"],
|
||||
alias_source_key: "help",
|
||||
default_aliases: &["hp"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::db::{DbPoolKey, get_help_aliases_enabled, set_help_aliases_enabled};
|
||||
|
||||
pub async fn handle_helpalias(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
if args.is_empty() {
|
||||
let enabled = get_help_aliases_enabled(&pool, bot_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(true);
|
||||
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Aliases dans help")
|
||||
.description(format!(
|
||||
"État actuel: `{}`",
|
||||
if enabled { "on" } else { "off" }
|
||||
))
|
||||
.color(0x5865F2);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let enabled = match args[0].to_lowercase().as_str() {
|
||||
"on" | "true" | "yes" => true,
|
||||
"off" | "false" | "no" => false,
|
||||
_ => {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+helpalias <on/off>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let _ = set_help_aliases_enabled(&pool, bot_id, enabled).await;
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Help aliases mis à jour")
|
||||
.description(format!(
|
||||
"Aliases dans l'aide: `{}`",
|
||||
if enabled { "on" } else { "off" }
|
||||
))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
}
|
||||
pub struct HelpaliasCommand;
|
||||
pub static COMMAND_DESCRIPTOR: HelpaliasCommand = HelpaliasCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for HelpaliasCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "helpalias",
|
||||
command: "helpalias",
|
||||
category: "permissions",
|
||||
params: "<on|off>",
|
||||
summary: "Active ou coupe les aliases help",
|
||||
description: "Active ou desactive laffichage des aliases dans laide.",
|
||||
examples: &["+helpalias", "+hs", "+help helpalias"],
|
||||
alias_source_key: "helpalias",
|
||||
default_aliases: &["hal"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::db::{
|
||||
DbPoolKey, get_help_aliases_enabled, get_help_perms_enabled, get_help_type,
|
||||
set_help_aliases_enabled, set_help_perms_enabled, set_help_type,
|
||||
};
|
||||
|
||||
pub async fn handle_helpsetting(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
if args.is_empty() {
|
||||
let help_type = get_help_type(&pool, bot_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_else(|| "button".to_string());
|
||||
let help_aliases = get_help_aliases_enabled(&pool, bot_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(true);
|
||||
let help_perms = get_help_perms_enabled(&pool, bot_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(true);
|
||||
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Configuration de l'aide")
|
||||
.description("Paramètres actuels:")
|
||||
.field("Mode d'affichage", format!("`{}`", help_type), true)
|
||||
.field("Aliases", format!("`{}`", if help_aliases { "on" } else { "off" }), true)
|
||||
.field(
|
||||
"Permissions",
|
||||
format!("`{}`", if help_perms { "on" } else { "off" }),
|
||||
true,
|
||||
)
|
||||
.color(0x5865F2);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
match args[0].to_lowercase().as_str() {
|
||||
"type" | "mode" => {
|
||||
if args.len() < 2 {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+helpsetting type <button|select|hybrid>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let normalized = match args[1].to_lowercase().as_str() {
|
||||
"button" => "button",
|
||||
"select" => "select",
|
||||
"hybrid" => "hybrid",
|
||||
_ => {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Valeurs valides: `button`, `select`, `hybrid`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let _ = set_help_type(&pool, bot_id, normalized).await;
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Mode de help mis à jour")
|
||||
.description(format!("Nouveau mode: `{}`", normalized))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
"aliases" | "alias" => {
|
||||
if args.len() < 2 {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+helpsetting aliases <on|off>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let enabled = match args[1].to_lowercase().as_str() {
|
||||
"on" | "true" | "yes" => true,
|
||||
"off" | "false" | "no" => false,
|
||||
_ => {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Valeurs valides: `on`, `off`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let _ = set_help_aliases_enabled(&pool, bot_id, enabled).await;
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Aliases de help mis à jour")
|
||||
.description(format!("Aliases: `{}`", if enabled { "on" } else { "off" }))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
"perms" | "permissions" => {
|
||||
if args.len() < 2 {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+helpsetting perms <on|off>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let enabled = match args[1].to_lowercase().as_str() {
|
||||
"on" | "true" | "yes" => true,
|
||||
"off" | "false" | "no" => false,
|
||||
_ => {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Valeurs valides: `on`, `off`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let _ = set_help_perms_enabled(&pool, bot_id, enabled).await;
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Affichage des permissions mis à jour")
|
||||
.description(format!(
|
||||
"Permissions: `{}`",
|
||||
if enabled { "on" } else { "off" }
|
||||
))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
_ => {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Sous-commandes: `type`, `aliases`, `perms`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
}
|
||||
|
||||
pub struct HelpsettingCommand;
|
||||
pub static COMMAND_DESCRIPTOR: HelpsettingCommand = HelpsettingCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for HelpsettingCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "helpsetting",
|
||||
command: "helpsetting",
|
||||
category: "permissions",
|
||||
params: "<type|aliases|perms> [value]",
|
||||
summary: "Configure l'affichage du système d'aide",
|
||||
description: "Permet de configurer le mode d'affichage, l'affichage des alias et l'affichage des permissions du système d'aide.",
|
||||
examples: &["+helpsetting", "+helpsetting type hybrid", "+helpsetting perms off"],
|
||||
alias_source_key: "helpsetting",
|
||||
default_aliases: &["hs"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::db::{DbPoolKey, get_help_type, set_help_type};
|
||||
|
||||
pub async fn handle_helptype(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
if args.is_empty() {
|
||||
let current = get_help_type(&pool, bot_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_else(|| "button".to_string());
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Mode help")
|
||||
.description(format!(
|
||||
"Mode actuel: `{}`\nValeurs: `button`, `select`, `hybrid`",
|
||||
current
|
||||
))
|
||||
.color(0x5865F2);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let normalized = match args[0].to_lowercase().as_str() {
|
||||
"button" => "button",
|
||||
"select" => "select",
|
||||
"hybrid" => "hybrid",
|
||||
_ => {
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+helptype <button/select/hybrid>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let _ = set_help_type(&pool, bot_id, normalized).await;
|
||||
let embed = serenity::builder::CreateEmbed::new()
|
||||
.title("Mode help mis à jour")
|
||||
.description(format!("Nouveau mode: `{}`", normalized))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
}
|
||||
pub struct HelptypeCommand;
|
||||
pub static COMMAND_DESCRIPTOR: HelptypeCommand = HelptypeCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for HelptypeCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "helptype",
|
||||
command: "helptype",
|
||||
category: "permissions",
|
||||
params: "<button|select|hybrid>",
|
||||
summary: "Change le mode daffichage help",
|
||||
description: "Definit le mode daffichage de laide entre button, select et hybrid.",
|
||||
examples: &["+helptype", "+he", "+help helptype"],
|
||||
alias_source_key: "helptype",
|
||||
default_aliases: &["htp"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::botconfig_common;
|
||||
|
||||
pub async fn handle_idle(ctx: &Context, msg: &Message) {
|
||||
botconfig_common::handle_status(ctx, msg, "+idle").await;
|
||||
}
|
||||
|
||||
pub struct IdleCommand;
|
||||
pub static COMMAND_DESCRIPTOR: IdleCommand = IdleCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for IdleCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "idle",
|
||||
command: "idle",
|
||||
category: "profile",
|
||||
params: "aucun",
|
||||
summary: "Passe le bot en idle",
|
||||
description: "Change le statut du bot en idle et sauvegarde ce statut.",
|
||||
examples: &["+idle", "+ie", "+help idle"],
|
||||
alias_source_key: "idle",
|
||||
default_aliases: &["idl"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::botconfig_common;
|
||||
|
||||
pub async fn handle_invisible(ctx: &Context, msg: &Message) {
|
||||
botconfig_common::handle_status(ctx, msg, "+invisible").await;
|
||||
}
|
||||
|
||||
pub struct InvisibleCommand;
|
||||
pub static COMMAND_DESCRIPTOR: InvisibleCommand = InvisibleCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for InvisibleCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "invisible",
|
||||
command: "invisible",
|
||||
category: "profile",
|
||||
params: "aucun",
|
||||
summary: "Passe le bot en invisible",
|
||||
description: "Change le statut du bot en invisible et sauvegarde ce statut.",
|
||||
examples: &["+invisible", "+ie", "+help invisible"],
|
||||
alias_source_key: "invisible",
|
||||
default_aliases: &["ivs"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::botconfig_common;
|
||||
|
||||
pub async fn handle_listen(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
botconfig_common::handle_activity(ctx, msg, "+listen", args).await;
|
||||
}
|
||||
|
||||
pub struct ListenCommand;
|
||||
pub static COMMAND_DESCRIPTOR: ListenCommand = ListenCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for ListenCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "listen",
|
||||
command: "listen",
|
||||
category: "profile",
|
||||
params: "<texte[, ,texte2,...]>",
|
||||
summary: "Definit une activite listening",
|
||||
description: "Configure la rotation des messages d activite en mode listening.",
|
||||
examples: &["+listen", "+ln", "+help listen"],
|
||||
alias_source_key: "listen",
|
||||
default_aliases: &["lsn"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::advanced_tools;
|
||||
|
||||
pub async fn handle_loading(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
advanced_tools::handle_loading(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct LoadingCommand;
|
||||
pub static COMMAND_DESCRIPTOR: LoadingCommand = LoadingCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for LoadingCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "loading",
|
||||
command: "loading",
|
||||
category: "general",
|
||||
params: "<duree> <message>",
|
||||
summary: "Affiche une barre de chargement",
|
||||
description: "Anime une barre de progression avec la duree et le texte fournis.",
|
||||
examples: &["+loading 10s Traitement en cours"],
|
||||
alias_source_key: "loading",
|
||||
default_aliases: &["loadbar", "bar"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::perms_service;
|
||||
|
||||
pub async fn handle_mainprefix(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
perms_service::handle_mainprefix(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct MainprefixCommand;
|
||||
pub static COMMAND_DESCRIPTOR: MainprefixCommand = MainprefixCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for MainprefixCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "mainprefix",
|
||||
command: "mainprefix",
|
||||
category: "permissions",
|
||||
params: "<prefix>",
|
||||
summary: "Change le prefixe global",
|
||||
description: "Definit le prefixe principal utilise par le bot sur tous les serveurs.",
|
||||
examples: &["+mainprefix", "+mx", "+help mainprefix"],
|
||||
alias_source_key: "mainprefix",
|
||||
default_aliases: &["mpx"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{discord_ts, send_embed, truncate_text};
|
||||
|
||||
pub async fn handle_member(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let member = if args.is_empty() {
|
||||
guild_id.member(&ctx.http, msg.author.id).await.ok()
|
||||
} else {
|
||||
let user_id = args[0]
|
||||
.trim_start_matches('<')
|
||||
.trim_end_matches('>')
|
||||
.trim_start_matches('@')
|
||||
.trim_start_matches('!')
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.map(UserId::new);
|
||||
|
||||
if let Some(uid) = user_id {
|
||||
guild_id.member(&ctx.http, uid).await.ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let Some(member) = member else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Membre non trouvé.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let joined_at = discord_ts(
|
||||
member.joined_at.unwrap_or_else(|| member.user.created_at()),
|
||||
"F",
|
||||
);
|
||||
let created_at = discord_ts(member.user.created_at(), "F");
|
||||
let avatar_url = member.user.avatar_url().unwrap_or_default();
|
||||
|
||||
let roles_str = if member.roles.is_empty() {
|
||||
"@everyone".to_string()
|
||||
} else {
|
||||
let roles_list: Vec<String> = member
|
||||
.roles
|
||||
.iter()
|
||||
.map(|r| format!("<@&{}>", r.get()))
|
||||
.collect();
|
||||
truncate_text(&roles_list.join(", "), 1024)
|
||||
};
|
||||
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title(&member.user.name)
|
||||
.description(format!("ID: `{}`", member.user.id.get()))
|
||||
.color(0x5865F2)
|
||||
.thumbnail(&avatar_url)
|
||||
.field("Compte créé", created_at, true)
|
||||
.field("A rejoint", joined_at, true)
|
||||
.field("Rôles", roles_str, false);
|
||||
|
||||
if let Some(nick) = &member.nick {
|
||||
embed = embed.field("Surnom", nick, true);
|
||||
}
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub struct MemberCommand;
|
||||
pub static COMMAND_DESCRIPTOR: MemberCommand = MemberCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for MemberCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "member",
|
||||
command: "member",
|
||||
category: "general",
|
||||
params: "<@membre/ID>",
|
||||
summary: "Affiche le profil membre",
|
||||
description: "Affiche les informations dun membre dans le serveur courant.",
|
||||
examples: &["+member", "+mr", "+help member"],
|
||||
alias_source_key: "member",
|
||||
default_aliases: &["mbr"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
use crate::commands::command_contract::{CommandMetadata, CommandSpec};
|
||||
|
||||
pub mod addrole;
|
||||
pub mod admin_common;
|
||||
pub mod admin_service;
|
||||
pub mod advanced_tools;
|
||||
pub mod alias;
|
||||
pub mod alladmins;
|
||||
pub mod allbots;
|
||||
pub mod allperms;
|
||||
pub mod autobackup;
|
||||
pub mod autoconfiglog;
|
||||
pub mod autoreact;
|
||||
pub mod backup;
|
||||
pub mod ban;
|
||||
pub mod banlist;
|
||||
pub mod banner;
|
||||
pub mod bl;
|
||||
pub mod blinfo;
|
||||
pub mod boostembed;
|
||||
pub mod boosters;
|
||||
pub mod boostlog;
|
||||
pub mod botadmins;
|
||||
pub mod botconfig_common;
|
||||
pub mod botconfig_service;
|
||||
pub mod bringall;
|
||||
pub mod button;
|
||||
pub mod calc;
|
||||
pub mod change;
|
||||
pub mod changeall;
|
||||
pub mod channel;
|
||||
pub mod choose;
|
||||
pub mod cleanup;
|
||||
pub mod clear_all_sanctions;
|
||||
pub mod clear_bl;
|
||||
pub mod clear_messages;
|
||||
pub mod clear_owners;
|
||||
pub mod clear_perms;
|
||||
pub mod clear_sanctions;
|
||||
pub mod cmute;
|
||||
pub mod command_contract;
|
||||
pub mod common;
|
||||
pub mod compet;
|
||||
pub mod create;
|
||||
pub mod del;
|
||||
pub mod del_sanction;
|
||||
pub mod delrole;
|
||||
pub mod derank;
|
||||
pub mod discussion;
|
||||
pub mod dnd;
|
||||
pub mod embed;
|
||||
pub mod emoji;
|
||||
pub mod end;
|
||||
pub mod giveaway;
|
||||
pub mod help;
|
||||
pub mod helpsetting;
|
||||
pub mod hide;
|
||||
pub mod hideall;
|
||||
pub mod idle;
|
||||
pub mod invisible;
|
||||
pub mod invite;
|
||||
pub mod join;
|
||||
pub mod kick;
|
||||
pub mod leave;
|
||||
pub mod leave_settings;
|
||||
pub mod listen;
|
||||
pub mod loading;
|
||||
pub mod lock;
|
||||
pub mod lockall;
|
||||
pub mod logs_service;
|
||||
pub mod mainprefix;
|
||||
pub mod massiverole;
|
||||
pub mod member;
|
||||
pub mod messagelog;
|
||||
pub mod moderation_tools;
|
||||
pub mod modlog;
|
||||
pub mod mp;
|
||||
pub mod mute;
|
||||
pub mod mutelist;
|
||||
pub mod newsticker;
|
||||
pub mod nolog;
|
||||
pub mod online;
|
||||
pub mod owner;
|
||||
pub mod perms;
|
||||
pub mod perms_service;
|
||||
pub mod pic;
|
||||
pub mod ping;
|
||||
pub mod playto;
|
||||
pub mod prefix;
|
||||
pub mod raidlog;
|
||||
pub mod remove_activity;
|
||||
pub mod renew;
|
||||
pub mod reroll;
|
||||
pub mod role;
|
||||
pub mod rolelog;
|
||||
pub mod rolemembers;
|
||||
pub mod sanctions;
|
||||
pub mod say;
|
||||
pub mod server;
|
||||
pub mod serverinfo;
|
||||
pub mod set;
|
||||
pub mod set_boostembed;
|
||||
pub mod set_modlogs;
|
||||
pub mod shadowbot;
|
||||
pub mod snipe;
|
||||
pub mod stream;
|
||||
pub mod sync;
|
||||
pub mod tempban;
|
||||
pub mod tempcmute;
|
||||
pub mod tempmute;
|
||||
pub mod temprole;
|
||||
pub mod theme;
|
||||
pub mod unban;
|
||||
pub mod unbanall;
|
||||
pub mod unbl;
|
||||
pub mod uncmute;
|
||||
pub mod unhide;
|
||||
pub mod unhideall;
|
||||
pub mod unlock;
|
||||
pub mod unlockall;
|
||||
pub mod unmassiverole;
|
||||
pub mod unmute;
|
||||
pub mod unmuteall;
|
||||
pub mod unowner;
|
||||
pub mod untemprole;
|
||||
pub mod user;
|
||||
pub mod viewlogs;
|
||||
pub mod vocinfo;
|
||||
pub mod voicekick;
|
||||
pub mod ticket;
|
||||
pub mod tickets;
|
||||
pub mod showpics;
|
||||
pub mod suggestion;
|
||||
pub mod autopublish;
|
||||
pub mod tempvoc;
|
||||
pub mod tempvoc_cmd;
|
||||
pub mod voicelog;
|
||||
pub mod voicemove;
|
||||
pub mod warn;
|
||||
pub mod claim;
|
||||
pub mod close;
|
||||
pub mod rename;
|
||||
pub mod ticket_member;
|
||||
pub mod watch;
|
||||
|
||||
pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
||||
vec![
|
||||
ping::COMMAND_DESCRIPTOR.metadata(),
|
||||
allbots::COMMAND_DESCRIPTOR.metadata(),
|
||||
alladmins::COMMAND_DESCRIPTOR.metadata(),
|
||||
botadmins::COMMAND_DESCRIPTOR.metadata(),
|
||||
boosters::COMMAND_DESCRIPTOR.metadata(),
|
||||
rolemembers::COMMAND_DESCRIPTOR.metadata(),
|
||||
serverinfo::COMMAND_DESCRIPTOR.metadata(),
|
||||
vocinfo::COMMAND_DESCRIPTOR.metadata(),
|
||||
role::COMMAND_DESCRIPTOR.metadata(),
|
||||
join::COMMAND_DESCRIPTOR.metadata(),
|
||||
channel::COMMAND_DESCRIPTOR.metadata(),
|
||||
user::COMMAND_DESCRIPTOR.metadata(),
|
||||
member::COMMAND_DESCRIPTOR.metadata(),
|
||||
pic::COMMAND_DESCRIPTOR.metadata(),
|
||||
banner::COMMAND_DESCRIPTOR.metadata(),
|
||||
server::COMMAND_DESCRIPTOR.metadata(),
|
||||
snipe::COMMAND_DESCRIPTOR.metadata(),
|
||||
emoji::COMMAND_DESCRIPTOR.metadata(),
|
||||
giveaway::COMMAND_DESCRIPTOR.metadata(),
|
||||
modlog::COMMAND_DESCRIPTOR.metadata(),
|
||||
messagelog::COMMAND_DESCRIPTOR.metadata(),
|
||||
voicelog::COMMAND_DESCRIPTOR.metadata(),
|
||||
boostlog::COMMAND_DESCRIPTOR.metadata(),
|
||||
rolelog::COMMAND_DESCRIPTOR.metadata(),
|
||||
raidlog::COMMAND_DESCRIPTOR.metadata(),
|
||||
autoconfiglog::COMMAND_DESCRIPTOR.metadata(),
|
||||
boostembed::COMMAND_DESCRIPTOR.metadata(),
|
||||
nolog::COMMAND_DESCRIPTOR.metadata(),
|
||||
set_modlogs::COMMAND_DESCRIPTOR.metadata(),
|
||||
set_boostembed::COMMAND_DESCRIPTOR.metadata(),
|
||||
sanctions::COMMAND_DESCRIPTOR.metadata(),
|
||||
end::COMMAND_DESCRIPTOR.metadata(),
|
||||
reroll::COMMAND_DESCRIPTOR.metadata(),
|
||||
choose::COMMAND_DESCRIPTOR.metadata(),
|
||||
embed::COMMAND_DESCRIPTOR.metadata(),
|
||||
clear_messages::COMMAND_DESCRIPTOR.metadata(),
|
||||
backup::COMMAND_DESCRIPTOR.metadata(),
|
||||
autobackup::COMMAND_DESCRIPTOR.metadata(),
|
||||
loading::COMMAND_DESCRIPTOR.metadata(),
|
||||
create::COMMAND_DESCRIPTOR.metadata(),
|
||||
newsticker::COMMAND_DESCRIPTOR.metadata(),
|
||||
massiverole::COMMAND_DESCRIPTOR.metadata(),
|
||||
unmassiverole::COMMAND_DESCRIPTOR.metadata(),
|
||||
voicemove::COMMAND_DESCRIPTOR.metadata(),
|
||||
voicekick::COMMAND_DESCRIPTOR.metadata(),
|
||||
cleanup::COMMAND_DESCRIPTOR.metadata(),
|
||||
bringall::COMMAND_DESCRIPTOR.metadata(),
|
||||
renew::COMMAND_DESCRIPTOR.metadata(),
|
||||
unbanall::COMMAND_DESCRIPTOR.metadata(),
|
||||
warn::COMMAND_DESCRIPTOR.metadata(),
|
||||
mute::COMMAND_DESCRIPTOR.metadata(),
|
||||
tempmute::COMMAND_DESCRIPTOR.metadata(),
|
||||
unmute::COMMAND_DESCRIPTOR.metadata(),
|
||||
cmute::COMMAND_DESCRIPTOR.metadata(),
|
||||
tempcmute::COMMAND_DESCRIPTOR.metadata(),
|
||||
uncmute::COMMAND_DESCRIPTOR.metadata(),
|
||||
mutelist::COMMAND_DESCRIPTOR.metadata(),
|
||||
unmuteall::COMMAND_DESCRIPTOR.metadata(),
|
||||
kick::COMMAND_DESCRIPTOR.metadata(),
|
||||
ban::COMMAND_DESCRIPTOR.metadata(),
|
||||
tempban::COMMAND_DESCRIPTOR.metadata(),
|
||||
unban::COMMAND_DESCRIPTOR.metadata(),
|
||||
banlist::COMMAND_DESCRIPTOR.metadata(),
|
||||
lock::COMMAND_DESCRIPTOR.metadata(),
|
||||
unlock::COMMAND_DESCRIPTOR.metadata(),
|
||||
lockall::COMMAND_DESCRIPTOR.metadata(),
|
||||
unlockall::COMMAND_DESCRIPTOR.metadata(),
|
||||
hide::COMMAND_DESCRIPTOR.metadata(),
|
||||
unhide::COMMAND_DESCRIPTOR.metadata(),
|
||||
hideall::COMMAND_DESCRIPTOR.metadata(),
|
||||
unhideall::COMMAND_DESCRIPTOR.metadata(),
|
||||
addrole::COMMAND_DESCRIPTOR.metadata(),
|
||||
delrole::COMMAND_DESCRIPTOR.metadata(),
|
||||
derank::COMMAND_DESCRIPTOR.metadata(),
|
||||
del_sanction::COMMAND_DESCRIPTOR.metadata(),
|
||||
clear_sanctions::COMMAND_DESCRIPTOR.metadata(),
|
||||
clear_all_sanctions::COMMAND_DESCRIPTOR.metadata(),
|
||||
temprole::COMMAND_DESCRIPTOR.metadata(),
|
||||
untemprole::COMMAND_DESCRIPTOR.metadata(),
|
||||
sync::COMMAND_DESCRIPTOR.metadata(),
|
||||
button::COMMAND_DESCRIPTOR.metadata(),
|
||||
autoreact::COMMAND_DESCRIPTOR.metadata(),
|
||||
calc::COMMAND_DESCRIPTOR.metadata(),
|
||||
shadowbot::COMMAND_DESCRIPTOR.metadata(),
|
||||
set::COMMAND_DESCRIPTOR.metadata(),
|
||||
theme::COMMAND_DESCRIPTOR.metadata(),
|
||||
playto::COMMAND_DESCRIPTOR.metadata(),
|
||||
listen::COMMAND_DESCRIPTOR.metadata(),
|
||||
watch::COMMAND_DESCRIPTOR.metadata(),
|
||||
compet::COMMAND_DESCRIPTOR.metadata(),
|
||||
stream::COMMAND_DESCRIPTOR.metadata(),
|
||||
remove_activity::COMMAND_DESCRIPTOR.metadata(),
|
||||
online::COMMAND_DESCRIPTOR.metadata(),
|
||||
idle::COMMAND_DESCRIPTOR.metadata(),
|
||||
dnd::COMMAND_DESCRIPTOR.metadata(),
|
||||
invisible::COMMAND_DESCRIPTOR.metadata(),
|
||||
owner::COMMAND_DESCRIPTOR.metadata(),
|
||||
unowner::COMMAND_DESCRIPTOR.metadata(),
|
||||
clear_owners::COMMAND_DESCRIPTOR.metadata(),
|
||||
bl::COMMAND_DESCRIPTOR.metadata(),
|
||||
unbl::COMMAND_DESCRIPTOR.metadata(),
|
||||
blinfo::COMMAND_DESCRIPTOR.metadata(),
|
||||
clear_bl::COMMAND_DESCRIPTOR.metadata(),
|
||||
say::COMMAND_DESCRIPTOR.metadata(),
|
||||
change::COMMAND_DESCRIPTOR.metadata(),
|
||||
changeall::COMMAND_DESCRIPTOR.metadata(),
|
||||
mainprefix::COMMAND_DESCRIPTOR.metadata(),
|
||||
prefix::COMMAND_DESCRIPTOR.metadata(),
|
||||
perms::COMMAND_DESCRIPTOR.metadata(),
|
||||
del::COMMAND_DESCRIPTOR.metadata(),
|
||||
clear_perms::COMMAND_DESCRIPTOR.metadata(),
|
||||
allperms::COMMAND_DESCRIPTOR.metadata(),
|
||||
help::COMMAND_DESCRIPTOR.metadata(),
|
||||
helpsetting::COMMAND_DESCRIPTOR.metadata(),
|
||||
alias::COMMAND_DESCRIPTOR.metadata(),
|
||||
mp::COMMAND_DESCRIPTOR.metadata(),
|
||||
invite::COMMAND_DESCRIPTOR.metadata(),
|
||||
leave::COMMAND_DESCRIPTOR.metadata(),
|
||||
leave_settings::COMMAND_DESCRIPTOR.metadata(),
|
||||
viewlogs::COMMAND_DESCRIPTOR.metadata(),
|
||||
discussion::COMMAND_DESCRIPTOR.metadata(),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn command_metadata_by_key(key: &str) -> Option<CommandMetadata> {
|
||||
all_command_metadata()
|
||||
.into_iter()
|
||||
.find(|meta| meta.key == key)
|
||||
}
|
||||
|
||||
pub fn resolve_default_alias(alias: &str) -> Option<&'static str> {
|
||||
let normalized = alias.trim().trim_start_matches('+').to_lowercase();
|
||||
all_command_metadata().into_iter().find_map(|meta| {
|
||||
if meta
|
||||
.default_aliases
|
||||
.iter()
|
||||
.any(|candidate| candidate.eq_ignore_ascii_case(&normalized))
|
||||
{
|
||||
Some(meta.key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
use serenity::builder::{
|
||||
CreateActionRow, CreateButton, CreateEmbed, CreateInteractionResponse,
|
||||
CreateInteractionResponseMessage, CreateMessage,
|
||||
};
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{
|
||||
add_list_fields, discord_ts, send_embed, theme_color, truncate_text,
|
||||
};
|
||||
use crate::db::{
|
||||
DbPoolKey, count_sent_mp_messages, get_mp_enabled, get_sent_mp_message, list_sent_mp_messages,
|
||||
log_sent_mp_message, mark_sent_mp_deleted, set_mp_enabled,
|
||||
};
|
||||
|
||||
pub async fn handle_mp(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if args
|
||||
.first()
|
||||
.map(|value| value.eq_ignore_ascii_case("settings"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
if args.len() == 1 {
|
||||
let enabled = get_mp_enabled(&pool, bot_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(true);
|
||||
let embed = CreateEmbed::new()
|
||||
.title("MP settings")
|
||||
.description(format!(
|
||||
"Envoi de MP: `{}`\nUtilise `+mp settings on/off`.",
|
||||
if enabled { "on" } else { "off" }
|
||||
))
|
||||
.color(0x5865F2);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let enabled = match args[1].to_lowercase().as_str() {
|
||||
"on" | "true" | "yes" => true,
|
||||
"off" | "false" | "no" => false,
|
||||
_ => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+mp settings <on/off>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let _ = set_mp_enabled(&pool, bot_id, enabled).await;
|
||||
let embed = CreateEmbed::new()
|
||||
.title("MP settings mis à jour")
|
||||
.description(format!(
|
||||
"Envoi de MP: `{}`",
|
||||
if enabled { "on" } else { "off" }
|
||||
))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args
|
||||
.first()
|
||||
.map(|value| value.eq_ignore_ascii_case("sent"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let page = args
|
||||
.get(1)
|
||||
.and_then(|value| value.parse::<i64>().ok())
|
||||
.filter(|value| *value >= 1)
|
||||
.unwrap_or(1);
|
||||
let _ = send_mp_sent_page(ctx, msg, page).await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args
|
||||
.first()
|
||||
.map(|value| value.eq_ignore_ascii_case("delete") || value.eq_ignore_ascii_case("del"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let Some(entry_id_raw) = args.get(1) else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+mp delete <id>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(entry_id) = entry_id_raw.parse::<i64>() else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("ID invalide.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(entry) = get_sent_mp_message(&pool, bot_id, entry_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Message MP introuvable.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let delete_result = ChannelId::new(entry.dm_channel_id as u64)
|
||||
.delete_message(&ctx.http, MessageId::new(entry.message_id as u64))
|
||||
.await;
|
||||
|
||||
let _ = mark_sent_mp_deleted(&pool, bot_id, entry_id).await;
|
||||
if delete_result.is_err() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("MP déjà supprimé ou inaccessible")
|
||||
.description(format!(
|
||||
"Entrée `#{}` marquée supprimée en base (Discord a refusé la suppression).",
|
||||
entry.entry_id
|
||||
))
|
||||
.color(0xFEE75C);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
} else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("MP supprimé")
|
||||
.description(format!("Entrée `#{}` supprimée.", entry.entry_id))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if args.len() < 2 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `+mp settings` ou `+mp <membre> <message>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(db_pool) = pool(ctx).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let enabled = get_mp_enabled(&db_pool, bot_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(true);
|
||||
if !enabled {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("MP désactivés")
|
||||
.description("Réactive-les avec `+mp settings on`.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(user_id) = parse_user_id(args[0]) else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Membre invalide.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let content = args[1..].join(" ");
|
||||
let message = format!("{}\n\n- envoyé par {}", content, msg.author.tag());
|
||||
|
||||
let result = user_id.create_dm_channel(&ctx.http).await;
|
||||
let Ok(channel) = result else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Impossible d'ouvrir le MP.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(sent_message) = channel.say(&ctx.http, message.clone()).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Impossible d'envoyer le MP.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(pool) = pool(ctx).await {
|
||||
let _ = log_sent_mp_message(
|
||||
&pool,
|
||||
bot_id,
|
||||
msg.author.id,
|
||||
user_id,
|
||||
channel.id,
|
||||
sent_message.id,
|
||||
&message,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Message privé envoyé")
|
||||
.description(format!("À <@{}>.", user_id.get()))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_mp_component(ctx: &Context, component: &ComponentInteraction) -> bool {
|
||||
let Some((owner_id, page)) = parse_mp_sent_custom_id(&component.data.custom_id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if component.user.id.get() != owner_id {
|
||||
let _ = component
|
||||
.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::Message(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.content("Seul l'auteur de la commande peut utiliser ces boutons.")
|
||||
.ephemeral(true),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let total = count_sent_mp_messages(&pool, bot_id)
|
||||
.await
|
||||
.unwrap_or(0)
|
||||
.max(0);
|
||||
let total_pages = ((total + MP_SENT_PAGE_SIZE - 1) / MP_SENT_PAGE_SIZE).max(1);
|
||||
let safe_page = page.clamp(1, total_pages);
|
||||
let offset = (safe_page - 1) * MP_SENT_PAGE_SIZE;
|
||||
let items = list_sent_mp_messages(&pool, bot_id, MP_SENT_PAGE_SIZE, offset)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let lines = items
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
let status = if entry.deleted_at.is_some() {
|
||||
"supprimé"
|
||||
} else {
|
||||
"actif"
|
||||
};
|
||||
let sent_at = discord_ts(
|
||||
Timestamp::from_unix_timestamp(entry.sent_at.timestamp())
|
||||
.unwrap_or_else(|_| Timestamp::now()),
|
||||
"F",
|
||||
);
|
||||
format!(
|
||||
"`#{}` · de <@{}> vers <@{}> · msg `{}` · {} · {} · {}",
|
||||
entry.entry_id,
|
||||
entry.sender_id,
|
||||
entry.recipient_id,
|
||||
entry.message_id,
|
||||
status,
|
||||
sent_at,
|
||||
truncate_text(&entry.content, 80)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let prev_page = if safe_page > 1 { safe_page - 1 } else { 1 };
|
||||
let next_page = if safe_page < total_pages {
|
||||
safe_page + 1
|
||||
} else {
|
||||
total_pages
|
||||
};
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title("MP envoyés")
|
||||
.description(format!(
|
||||
"{} message(s) · Page {}/{}",
|
||||
total, safe_page, total_pages
|
||||
))
|
||||
.color(theme_color(ctx).await);
|
||||
embed = add_list_fields(embed, &lines, "Messages");
|
||||
|
||||
let components = vec![CreateActionRow::Buttons(vec![
|
||||
CreateButton::new(format!("mpsent:{}:{}", component.user.id.get(), prev_page))
|
||||
.label("◀ Précédent")
|
||||
.style(ButtonStyle::Primary)
|
||||
.disabled(safe_page <= 1),
|
||||
CreateButton::new(format!("mpsent:{}:{}", component.user.id.get(), next_page))
|
||||
.label("Suivant ▶")
|
||||
.style(ButtonStyle::Primary)
|
||||
.disabled(safe_page >= total_pages),
|
||||
])];
|
||||
|
||||
let _ = component
|
||||
.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::UpdateMessage(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.embed(embed)
|
||||
.components(components),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
const MP_SENT_PAGE_SIZE: i64 = 10;
|
||||
|
||||
async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
}
|
||||
|
||||
async fn send_mp_sent_page(ctx: &Context, msg: &Message, page: i64) -> Result<(), ()> {
|
||||
let bot_id = ctx.cache.current_user().id;
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return Err(());
|
||||
};
|
||||
|
||||
let total = count_sent_mp_messages(&pool, bot_id)
|
||||
.await
|
||||
.unwrap_or(0)
|
||||
.max(0);
|
||||
let total_pages = ((total + MP_SENT_PAGE_SIZE - 1) / MP_SENT_PAGE_SIZE).max(1);
|
||||
let safe_page = page.clamp(1, total_pages) as i64;
|
||||
let offset = (safe_page - 1) * MP_SENT_PAGE_SIZE;
|
||||
let items = list_sent_mp_messages(&pool, bot_id, MP_SENT_PAGE_SIZE, offset)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let lines = items
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
let status = if entry.deleted_at.is_some() {
|
||||
"supprimé"
|
||||
} else {
|
||||
"actif"
|
||||
};
|
||||
let sent_at = discord_ts(
|
||||
Timestamp::from_unix_timestamp(entry.sent_at.timestamp())
|
||||
.unwrap_or_else(|_| Timestamp::now()),
|
||||
"F",
|
||||
);
|
||||
format!(
|
||||
"`#{}` · de <@{}> vers <@{}> · msg `{}` · {} · {} · {}",
|
||||
entry.entry_id,
|
||||
entry.sender_id,
|
||||
entry.recipient_id,
|
||||
entry.message_id,
|
||||
status,
|
||||
sent_at,
|
||||
truncate_text(&entry.content, 80)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let prev_page = if safe_page > 1 { safe_page - 1 } else { 1 };
|
||||
let next_page = if safe_page < total_pages {
|
||||
safe_page + 1
|
||||
} else {
|
||||
total_pages
|
||||
};
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title("MP envoyés")
|
||||
.description(format!(
|
||||
"{} message(s) · Page {}/{}",
|
||||
total, safe_page, total_pages
|
||||
))
|
||||
.color(theme_color(ctx).await);
|
||||
|
||||
embed = add_list_fields(embed, &lines, "Messages");
|
||||
|
||||
let components = vec![CreateActionRow::Buttons(vec![
|
||||
CreateButton::new(format!("mpsent:{}:{}", msg.author.id.get(), prev_page))
|
||||
.label("◀ Précédent")
|
||||
.style(ButtonStyle::Primary)
|
||||
.disabled(safe_page <= 1),
|
||||
CreateButton::new(format!("mpsent:{}:{}", msg.author.id.get(), next_page))
|
||||
.label("Suivant ▶")
|
||||
.style(ButtonStyle::Primary)
|
||||
.disabled(safe_page >= total_pages),
|
||||
])];
|
||||
|
||||
let _ = msg
|
||||
.channel_id
|
||||
.send_message(
|
||||
&ctx.http,
|
||||
CreateMessage::new().embed(embed).components(components),
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_mp_sent_custom_id(custom_id: &str) -> Option<(u64, i64)> {
|
||||
let parts = custom_id.split(':').collect::<Vec<_>>();
|
||||
if parts.len() != 3 || parts.first().copied()? != "mpsent" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let owner_id = parts.get(1)?.parse::<u64>().ok()?;
|
||||
let page = parts.get(2)?.parse::<i64>().ok()?;
|
||||
Some((owner_id, page))
|
||||
}
|
||||
|
||||
fn parse_user_id(input: &str) -> Option<UserId> {
|
||||
let cleaned = input
|
||||
.trim()
|
||||
.trim_start_matches('<')
|
||||
.trim_end_matches('>')
|
||||
.trim_start_matches('@')
|
||||
.trim_start_matches('!');
|
||||
|
||||
cleaned.parse::<u64>().ok().map(UserId::new)
|
||||
}
|
||||
pub struct MpCommand;
|
||||
pub static COMMAND_DESCRIPTOR: MpCommand = MpCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for MpCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "mp",
|
||||
command: "mp",
|
||||
category: "profile",
|
||||
params: "settings [on|off] | sent [page] | delete <id> | <@membre/ID> <message...>",
|
||||
summary: "Gere lenvoi de messages prives",
|
||||
description: "Permet de configurer, envoyer, lister et supprimer des messages prives envoyes.",
|
||||
examples: &["+mp", "+help mp"],
|
||||
alias_source_key: "mp",
|
||||
default_aliases: &["dmsg"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::botconfig_common;
|
||||
|
||||
pub async fn handle_online(ctx: &Context, msg: &Message) {
|
||||
botconfig_common::handle_status(ctx, msg, "+online").await;
|
||||
}
|
||||
|
||||
pub struct OnlineCommand;
|
||||
pub static COMMAND_DESCRIPTOR: OnlineCommand = OnlineCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for OnlineCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "online",
|
||||
command: "online",
|
||||
category: "profile",
|
||||
params: "aucun",
|
||||
summary: "Passe le bot en online",
|
||||
description: "Change le statut du bot en online et sauvegarde ce statut.",
|
||||
examples: &["+online", "+oe", "+help online"],
|
||||
alias_source_key: "online",
|
||||
default_aliases: &["onl"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
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"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::perms_service;
|
||||
|
||||
pub async fn handle_perms(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
perms_service::handle_perms(ctx, msg, args).await;
|
||||
}
|
||||
|
||||
pub struct PermsCommand;
|
||||
pub static COMMAND_DESCRIPTOR: PermsCommand = PermsCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for PermsCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "perms",
|
||||
command: "perms",
|
||||
category: "permissions",
|
||||
params: "aucun",
|
||||
summary: "Affiche les permissions ACL",
|
||||
description: "Affiche les permissions ACL configurees par role ou scope.",
|
||||
examples: &["+perms", "+ps", "+help perms"],
|
||||
alias_source_key: "perms",
|
||||
default_aliases: &["prm"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,667 @@
|
||||
use serenity::builder::{
|
||||
CreateActionRow, CreateButton, CreateEmbed, CreateInteractionResponse,
|
||||
CreateInteractionResponseMessage, CreateMessage,
|
||||
};
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{add_list_fields, send_embed, theme_color, truncate_text};
|
||||
use crate::db::{
|
||||
DbPoolKey, clear_role_permissions, grant_command_access, grant_perm_level,
|
||||
list_role_command_access, list_role_perm_levels, list_role_scopes, remove_scope_permissions,
|
||||
reset_command_permissions, set_command_permission, set_guild_prefix, set_main_prefix,
|
||||
};
|
||||
use crate::permissions::{
|
||||
all_command_keys, command_required_permission, default_permission, is_owner_user,
|
||||
};
|
||||
|
||||
const ALLPERMS_PAGE_SIZE: usize = 12;
|
||||
const ALLPERMS_CUSTOM_ID_PREFIX: &str = "allperms";
|
||||
|
||||
fn parse_user_or_role(input: &str) -> Option<(&'static str, u64)> {
|
||||
let trimmed = input.trim();
|
||||
if trimmed.starts_with("<@&") && trimmed.ends_with('>') {
|
||||
return trimmed
|
||||
.trim_start_matches("<@&")
|
||||
.trim_end_matches('>')
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.map(|id| ("role", id));
|
||||
}
|
||||
|
||||
if (trimmed.starts_with("<@") && trimmed.ends_with('>')) || trimmed.parse::<u64>().is_ok() {
|
||||
let cleaned = trimmed
|
||||
.trim_start_matches('<')
|
||||
.trim_end_matches('>')
|
||||
.trim_start_matches('@')
|
||||
.trim_start_matches('!');
|
||||
if let Ok(id) = cleaned.parse::<u64>() {
|
||||
return Some(("user", id));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn normalize_command_name(input: &str) -> String {
|
||||
input
|
||||
.trim_start_matches('+')
|
||||
.replace(' ', "_")
|
||||
.to_lowercase()
|
||||
}
|
||||
|
||||
async fn ensure_owner(ctx: &Context, msg: &Message) -> bool {
|
||||
if is_owner_user(ctx, msg.author.id).await {
|
||||
true
|
||||
} else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Accès refusé")
|
||||
.description("Commande réservée aux owners.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_change(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if !ensure_owner(ctx, msg).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 = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
if args
|
||||
.first()
|
||||
.map(|s| s.eq_ignore_ascii_case("reset"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let removed = reset_command_permissions(&pool, bot_id).await.unwrap_or(0);
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Permissions réinitialisées")
|
||||
.description(format!("Overrides supprimés: {}", removed))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args.len() < 2 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `change <commande> <permission>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let command = normalize_command_name(args[0]);
|
||||
let Ok(level) = args[1].parse::<u8>() else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Permission invalide (0..9).`).")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
if level > 9 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Permission invalide (0..9).`).")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let _ = set_command_permission(&pool, bot_id, &command, level).await;
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Permission modifiée")
|
||||
.description(format!("`{}` -> niveau `{}`", command, level))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_changeall(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if !ensure_owner(ctx, msg).await {
|
||||
return;
|
||||
}
|
||||
|
||||
if args.len() < 2 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `changeall <permission> <permission>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(from) = args[0].parse::<u8>() else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Permission source invalide.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
let Ok(to) = args[1].parse::<u8>() else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Permission cible invalide.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
if from > 9 || to > 9 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Permissions valides: 0..9")
|
||||
.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 = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let mut updated = 0usize;
|
||||
for cmd in all_command_keys() {
|
||||
let current = command_required_permission(ctx, &cmd).await;
|
||||
if current == from {
|
||||
let _ = set_command_permission(&pool, bot_id, &cmd, to).await;
|
||||
updated += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Changeall appliqué")
|
||||
.description(format!("{} commande(s): {} -> {}", updated, from, to))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_mainprefix(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if !ensure_owner(ctx, msg).await {
|
||||
return;
|
||||
}
|
||||
|
||||
if args.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `mainprefix <préfixe>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let prefix = args[0].trim();
|
||||
if prefix.is_empty() || prefix.len() > 5 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Préfixe invalide (1 à 5 caractères).`).")
|
||||
.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()
|
||||
};
|
||||
if let Some(pool) = pool {
|
||||
let _ = set_main_prefix(&pool, bot_id, prefix).await;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Préfixe principal mis à jour")
|
||||
.description(format!("Nouveau préfixe principal: `{}`", prefix))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_prefix(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if !ensure_owner(ctx, msg).await {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Commande disponible uniquement sur un serveur.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
if args.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `prefix <préfixe>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let prefix = args[0].trim();
|
||||
if prefix.is_empty() || prefix.len() > 5 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Préfixe invalide (1 à 5 caractères).`).")
|
||||
.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()
|
||||
};
|
||||
|
||||
if let Some(pool) = pool {
|
||||
let _ = set_guild_prefix(&pool, bot_id, guild_id, prefix).await;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Préfixe serveur mis à jour")
|
||||
.description(format!("Nouveau préfixe ici: `{}`", prefix))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_set_perm(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if !ensure_owner(ctx, msg).await {
|
||||
return;
|
||||
}
|
||||
|
||||
if args.len() < 3 || !args[0].eq_ignore_ascii_case("perm") {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `set perm <permission/commande> <rôle/membre>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let target = parse_user_or_role(args[2]);
|
||||
let Some((scope_type, scope_id)) = target else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Rôle/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 = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
if let Ok(level) = args[1].parse::<u8>() {
|
||||
if level > 9 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Permission invalide (0..9).`).")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let _ = grant_perm_level(&pool, bot_id, scope_type, scope_id, level).await;
|
||||
let who = if scope_type == "role" {
|
||||
format!("<@&{}>", scope_id)
|
||||
} else {
|
||||
format!("<@{}>", scope_id)
|
||||
};
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Permission attribuée")
|
||||
.description(format!("{} reçoit la permission `{}`", who, level))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let command = normalize_command_name(args[1]);
|
||||
let _ = grant_command_access(&pool, bot_id, scope_type, scope_id, &command).await;
|
||||
let who = if scope_type == "role" {
|
||||
format!("<@&{}>", scope_id)
|
||||
} else {
|
||||
format!("<@{}>", scope_id)
|
||||
};
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Accès commande attribué")
|
||||
.description(format!("{} reçoit l'accès direct à `{}`", who, command))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_del_perm(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
if !ensure_owner(ctx, msg).await {
|
||||
return;
|
||||
}
|
||||
|
||||
if args.len() < 2 || !args[0].eq_ignore_ascii_case("perm") {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Usage: `del perm <rôle>`")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let target = parse_user_or_role(args[1]);
|
||||
let Some((scope_type, scope_id)) = target else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Rôle/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 = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let removed = remove_scope_permissions(&pool, bot_id, scope_type, scope_id)
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Permissions supprimées")
|
||||
.description(format!("{} entrée(s) supprimée(s).", removed))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_clear_perms(ctx: &Context, msg: &Message) {
|
||||
if !ensure_owner(ctx, msg).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 = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let removed = clear_role_permissions(&pool, bot_id).await.unwrap_or(0);
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Permissions rôles supprimées")
|
||||
.description(format!("{} entrée(s) supprimée(s).", removed))
|
||||
.color(0x57F287);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_perms(ctx: &Context, msg: &Message, _args: &[&str]) {
|
||||
if !ensure_owner(ctx, msg).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 = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("DB indisponible.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let roles = list_role_scopes(&pool, bot_id).await.unwrap_or_default();
|
||||
let mut lines = Vec::new();
|
||||
|
||||
for rid in roles {
|
||||
let perm_levels = list_role_perm_levels(&pool, bot_id, rid as u64)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let command_access = list_role_command_access(&pool, bot_id, rid as u64)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let perms = if perm_levels.is_empty() {
|
||||
"aucun".to_string()
|
||||
} else {
|
||||
perm_levels
|
||||
.iter()
|
||||
.map(|p| p.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",")
|
||||
};
|
||||
|
||||
let commands = if command_access.is_empty() {
|
||||
"aucune".to_string()
|
||||
} else {
|
||||
truncate_text(&command_access.join(", "), 80)
|
||||
};
|
||||
|
||||
lines.push(format!(
|
||||
"<@&{}> · perms [{}] · cmd [{}]",
|
||||
rid, perms, commands
|
||||
));
|
||||
}
|
||||
|
||||
let mut embed = CreateEmbed::new().title("Permissions du bot");
|
||||
embed = add_list_fields(embed, &lines, "Rôles configurés");
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn handle_allperms(ctx: &Context, msg: &Message, _args: &[&str]) {
|
||||
if !ensure_owner(ctx, msg).await {
|
||||
return;
|
||||
}
|
||||
|
||||
let lines = collect_allperms_lines(ctx).await;
|
||||
let total_pages = total_pages_for(lines.len());
|
||||
let requested_page = _args
|
||||
.first()
|
||||
.and_then(|s| s.parse::<usize>().ok())
|
||||
.unwrap_or(1)
|
||||
.saturating_sub(1);
|
||||
let page = requested_page.min(total_pages.saturating_sub(1));
|
||||
|
||||
let color = theme_color(ctx).await;
|
||||
let embed = build_allperms_embed(&lines, page, color);
|
||||
let components = allperms_components(msg.author.id, page, total_pages);
|
||||
|
||||
let _ = msg
|
||||
.channel_id
|
||||
.send_message(
|
||||
&ctx.http,
|
||||
CreateMessage::new().embed(embed).components(components),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_allperms_component(ctx: &Context, component: &ComponentInteraction) -> bool {
|
||||
let Some((owner_id, requested_page)) = parse_allperms_custom_id(&component.data.custom_id)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if component.user.id.get() != owner_id {
|
||||
let _ = component
|
||||
.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::Message(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.content("Seul l'auteur de la commande peut utiliser ces boutons.")
|
||||
.ephemeral(true),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
|
||||
let lines = collect_allperms_lines(ctx).await;
|
||||
let total_pages = total_pages_for(lines.len());
|
||||
let page = requested_page.min(total_pages.saturating_sub(1));
|
||||
let color = theme_color(ctx).await;
|
||||
let embed = build_allperms_embed(&lines, page, color);
|
||||
let components = allperms_components(component.user.id, page, total_pages);
|
||||
|
||||
let _ = component
|
||||
.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::UpdateMessage(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.embed(embed)
|
||||
.components(components),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
async fn collect_allperms_lines(ctx: &Context) -> Vec<String> {
|
||||
let mut commands = all_command_keys();
|
||||
if !commands.iter().any(|c| c == "allperms") {
|
||||
commands.push("allperms".to_string());
|
||||
}
|
||||
commands.sort();
|
||||
|
||||
let mut lines = Vec::with_capacity(commands.len());
|
||||
for cmd in commands {
|
||||
let required = command_required_permission(ctx, &cmd).await;
|
||||
let default = default_permission(&cmd);
|
||||
|
||||
if required == default {
|
||||
lines.push(format!("`{}` -> `{}`", cmd, required));
|
||||
} else {
|
||||
lines.push(format!(
|
||||
"`{}` -> `{}` (défaut `{}`)",
|
||||
cmd, required, default
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
lines
|
||||
}
|
||||
|
||||
fn total_pages_for(total_items: usize) -> usize {
|
||||
((total_items + ALLPERMS_PAGE_SIZE.saturating_sub(1)) / ALLPERMS_PAGE_SIZE).max(1)
|
||||
}
|
||||
|
||||
fn build_allperms_embed(lines: &[String], page: usize, color: u32) -> CreateEmbed {
|
||||
let total_pages = total_pages_for(lines.len());
|
||||
let safe_page = page.min(total_pages.saturating_sub(1));
|
||||
let start = safe_page * ALLPERMS_PAGE_SIZE;
|
||||
let end = (start + ALLPERMS_PAGE_SIZE).min(lines.len());
|
||||
let chunk = if start < end { &lines[start..end] } else { &[] };
|
||||
|
||||
let value = if chunk.is_empty() {
|
||||
"Aucune commande.".to_string()
|
||||
} else {
|
||||
truncate_text(&chunk.join("\n"), 1024)
|
||||
};
|
||||
|
||||
CreateEmbed::new()
|
||||
.title("Permissions de toutes les commandes")
|
||||
.description(format!(
|
||||
"{} commande(s) · Page {}/{}",
|
||||
lines.len(),
|
||||
safe_page + 1,
|
||||
total_pages
|
||||
))
|
||||
.field("Niveaux requis", value, false)
|
||||
.color(color)
|
||||
}
|
||||
|
||||
fn allperms_components(owner_id: UserId, page: usize, total_pages: usize) -> Vec<CreateActionRow> {
|
||||
let safe_total = total_pages.max(1);
|
||||
let safe_page = page.min(safe_total.saturating_sub(1));
|
||||
let prev_page = safe_page.saturating_sub(1);
|
||||
let next_page = (safe_page + 1).min(safe_total.saturating_sub(1));
|
||||
|
||||
vec![CreateActionRow::Buttons(vec![
|
||||
CreateButton::new(format!(
|
||||
"{}:{}:{}",
|
||||
ALLPERMS_CUSTOM_ID_PREFIX,
|
||||
owner_id.get(),
|
||||
prev_page
|
||||
))
|
||||
.label("◀ Précédent")
|
||||
.style(ButtonStyle::Primary)
|
||||
.disabled(safe_page == 0),
|
||||
CreateButton::new(format!(
|
||||
"{}:{}:{}",
|
||||
ALLPERMS_CUSTOM_ID_PREFIX,
|
||||
owner_id.get(),
|
||||
next_page
|
||||
))
|
||||
.label("Suivant ▶")
|
||||
.style(ButtonStyle::Primary)
|
||||
.disabled(safe_page + 1 >= safe_total),
|
||||
])]
|
||||
}
|
||||
|
||||
fn parse_allperms_custom_id(custom_id: &str) -> Option<(u64, usize)> {
|
||||
let mut parts = custom_id.split(':');
|
||||
let prefix = parts.next()?;
|
||||
if prefix != ALLPERMS_CUSTOM_ID_PREFIX {
|
||||
return None;
|
||||
}
|
||||
|
||||
let owner_id = parts.next()?.parse::<u64>().ok()?;
|
||||
let page = parts.next()?.parse::<usize>().ok()?;
|
||||
Some((owner_id, page))
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
|
||||
pub async fn handle_pic(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let user = if args.is_empty() {
|
||||
msg.author.clone()
|
||||
} else {
|
||||
let user_id = args[0]
|
||||
.trim_start_matches('<')
|
||||
.trim_end_matches('>')
|
||||
.trim_start_matches('@')
|
||||
.trim_start_matches('!')
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.map(UserId::new);
|
||||
|
||||
if let Some(uid) = user_id {
|
||||
match ctx.http.get_user(uid).await {
|
||||
Ok(u) => u,
|
||||
Err(_) => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Utilisateur non trouvé.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Impossible de parser l'utilisateur.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let avatar_url = user.avatar_url().unwrap_or_default();
|
||||
|
||||
if avatar_url.is_empty() {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Erreur")
|
||||
.description("Cet utilisateur n'a pas de photo de profil.")
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title(format!("Photo de profil de {}", user.name))
|
||||
.image(avatar_url)
|
||||
.color(0x5865F2);
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub struct PicCommand;
|
||||
pub static COMMAND_DESCRIPTOR: PicCommand = PicCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for PicCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "pic",
|
||||
command: "pic",
|
||||
category: "general",
|
||||
params: "<@membre/ID>",
|
||||
summary: "Affiche la photo de profil",
|
||||
description: "Affiche la photo de profil dun utilisateur cible ou de lauteur.",
|
||||
examples: &["+pic", "+pc", "+help pic"],
|
||||
alias_source_key: "pic",
|
||||
default_aliases: &["pfp"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use serenity::builder::{CreateEmbed, CreateMessage, EditMessage};
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::commands::common::{has_flag, theme_color};
|
||||
|
||||
pub async fn handle_ping(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let detailed = has_flag(args, &["--details", "-d", "full"]);
|
||||
let color = theme_color(ctx).await;
|
||||
let start = Instant::now();
|
||||
|
||||
let pending_embed = CreateEmbed::new()
|
||||
.title("Pong")
|
||||
.description("Mesure de la latence en cours...")
|
||||
.color(color);
|
||||
|
||||
let sent = msg
|
||||
.channel_id
|
||||
.send_message(&ctx.http, CreateMessage::new().embed(pending_embed))
|
||||
.await;
|
||||
|
||||
let Ok(mut sent_message) = sent else {
|
||||
return;
|
||||
};
|
||||
|
||||
let latency_ms = start.elapsed().as_millis();
|
||||
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title("Pong")
|
||||
.description("Le bot répond correctement.")
|
||||
.color(color)
|
||||
.field("Latence", format!("{} ms", latency_ms), true);
|
||||
|
||||
if detailed {
|
||||
embed = embed.field("Canal", format!("<#{}>", msg.channel_id.get()), true);
|
||||
if let Some(guild_id) = msg.guild_id {
|
||||
embed = embed.field("Serveur", guild_id.to_string(), true);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = sent_message
|
||||
.edit(&ctx.http, EditMessage::new().embed(embed))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct PingCommand;
|
||||
pub static COMMAND_DESCRIPTOR: PingCommand = PingCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for PingCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "ping",
|
||||
command: "ping",
|
||||
category: "general",
|
||||
params: "aucun",
|
||||
summary: "Mesure la latence du bot",
|
||||
description: "Affiche le temps de reponse du bot et met a jour un embed avec la latence calculee.",
|
||||
examples: &["+ping", "+pg", "+help ping"],
|
||||
alias_source_key: "ping",
|
||||
default_aliases: &["pg"],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::botconfig_common;
|
||||
|
||||
pub async fn handle_playto(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
botconfig_common::handle_activity(ctx, msg, "+playto", args).await;
|
||||
}
|
||||
|
||||
pub struct PlaytoCommand;
|
||||
pub static COMMAND_DESCRIPTOR: PlaytoCommand = PlaytoCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for PlaytoCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
key: "playto",
|
||||
command: "playto",
|
||||
category: "profile",
|
||||
params: "<texte[, ,texte2,...]>",
|
||||
summary: "Definit une activite playing",
|
||||
description: "Configure la rotation des messages d activite en mode playing.",
|
||||
examples: &["+playto", "+po", "+help playto"],
|
||||
alias_source_key: "playto",
|
||||
default_aliases: &["ply"],
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user