diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 19dfb85..7205084 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -186,6 +186,8 @@ pub mod renew; pub mod reroll; #[path = "infos/role.rs"] pub mod role; +#[path = "roles/rolemenu.rs"] +pub mod rolemenu; #[path = "logs/rolelog.rs"] pub mod rolelog; #[path = "infos/rolemembers.rs"] @@ -214,6 +216,8 @@ pub mod snipe; pub mod stream; #[path = "outils/suggestion.rs"] pub mod suggestion; +#[path = "salons_vocal/slowmode.rs"] +pub mod slowmode; #[path = "roles/sync.rs"] pub mod sync; #[path = "moderation/tempban.rs"] @@ -287,6 +291,7 @@ pub fn all_command_metadata() -> Vec { botadmins::COMMAND_DESCRIPTOR.metadata(), boosters::COMMAND_DESCRIPTOR.metadata(), rolemembers::COMMAND_DESCRIPTOR.metadata(), + rolemenu::COMMAND_DESCRIPTOR.metadata(), serverinfo::COMMAND_DESCRIPTOR.metadata(), vocinfo::COMMAND_DESCRIPTOR.metadata(), role::COMMAND_DESCRIPTOR.metadata(), @@ -355,6 +360,7 @@ pub fn all_command_metadata() -> Vec { tempban::COMMAND_DESCRIPTOR.metadata(), unban::COMMAND_DESCRIPTOR.metadata(), banlist::COMMAND_DESCRIPTOR.metadata(), + slowmode::COMMAND_DESCRIPTOR.metadata(), lock::COMMAND_DESCRIPTOR.metadata(), unlock::COMMAND_DESCRIPTOR.metadata(), lockall::COMMAND_DESCRIPTOR.metadata(), diff --git a/src/commands/roles/rolemenu.rs b/src/commands/roles/rolemenu.rs new file mode 100644 index 0000000..fde0499 --- /dev/null +++ b/src/commands/roles/rolemenu.rs @@ -0,0 +1,597 @@ +use chrono::Utc; +use serenity::builder::{ + CreateActionRow, CreateButton, CreateEmbed, CreateInputText, CreateInteractionResponse, + CreateInteractionResponseMessage, CreateMessage, CreateModal, EditMessage, +}; +use serenity::model::application::{ + ActionRowComponent, ButtonKind, ButtonStyle, ComponentInteraction, InputTextStyle, + ModalInteraction, +}; +use serenity::model::prelude::*; +use serenity::prelude::*; + +use crate::commands::common::theme_color; + +const ROLEMENU_PANEL_PREFIX: &str = "rolemenu:panel"; +const ROLEMENU_MODAL_PREFIX: &str = "rolemenu:modal"; +const ROLEMENU_TOGGLE_PREFIX: &str = "rolemenu:toggle"; + +#[derive(Clone, Copy)] +struct RoleMenuTarget { + owner_id: u64, + channel_id: u64, + message_id: u64, +} + +fn panel_custom_id(action: &str, target: RoleMenuTarget) -> String { + format!( + "{}:{}:{}:{}:{}", + ROLEMENU_PANEL_PREFIX, action, target.owner_id, target.channel_id, target.message_id + ) +} + +fn modal_custom_id(action: &str, target: RoleMenuTarget) -> String { + format!( + "{}:{}:{}:{}:{}", + ROLEMENU_MODAL_PREFIX, action, target.owner_id, target.channel_id, target.message_id + ) +} + +fn parse_target_id(prefix_kind: &str, custom_id: &str) -> Option<(String, RoleMenuTarget)> { + let parts = custom_id.split(':').collect::>(); + if parts.len() != 6 { + return None; + } + + if parts[0] != "rolemenu" || parts[1] != prefix_kind { + return None; + } + + let action = parts[2].to_string(); + let owner_id = parts[3].parse::().ok()?; + let channel_id = parts[4].parse::().ok()?; + let message_id = parts[5].parse::().ok()?; + + Some(( + action, + RoleMenuTarget { + owner_id, + channel_id, + message_id, + }, + )) +} + +fn parse_panel_custom_id(custom_id: &str) -> Option<(String, RoleMenuTarget)> { + parse_target_id("panel", custom_id) +} + +fn parse_modal_custom_id(custom_id: &str) -> Option<(String, RoleMenuTarget)> { + parse_target_id("modal", custom_id) +} + +fn parse_toggle_role_id(custom_id: &str) -> Option { + let mut parts = custom_id.split(':'); + let root = parts.next()?; + let action = parts.next()?; + let role_id = parts.next()?.parse::().ok()?; + if root != "rolemenu" || action != "toggle" || parts.next().is_some() { + return None; + } + + Some(RoleId::new(role_id)) +} + +fn parse_role_id_input(raw: &str) -> Option { + let cleaned = raw + .trim() + .trim_start_matches("<@&") + .trim_end_matches('>'); + cleaned.parse::().ok().map(RoleId::new) +} + +fn parse_button_style(raw: &str) -> ButtonStyle { + match raw.trim().to_lowercase().as_str() { + "primary" | "blue" | "bleu" => ButtonStyle::Primary, + "success" | "green" | "vert" => ButtonStyle::Success, + "danger" | "red" | "rouge" => ButtonStyle::Danger, + _ => ButtonStyle::Secondary, + } +} + +fn parse_hex_color(raw: &str) -> Option { + let mut value = raw.trim(); + if value.is_empty() { + return None; + } + + if let Some(stripped) = value.strip_prefix('#') { + value = stripped; + } + if let Some(stripped) = value.strip_prefix("0x") { + value = stripped; + } + + if value.is_empty() { + return None; + } + + u32::from_str_radix(value, 16).ok() +} + +fn modal_value(modal: &ModalInteraction, wanted_id: &str) -> Option { + for row in &modal.data.components { + for component in &row.components { + if let ActionRowComponent::InputText(input) = component { + if input.custom_id == wanted_id { + return input.value.clone(); + } + } + } + } + None +} + +fn panel_components(target: RoleMenuTarget) -> Vec { + vec![CreateActionRow::Buttons(vec![ + CreateButton::new(panel_custom_id("embed", target)) + .label("Configurer l'embed") + .style(ButtonStyle::Primary), + CreateButton::new(panel_custom_id("addrole", target)) + .label("Ajouter bouton role") + .style(ButtonStyle::Success), + CreateButton::new(panel_custom_id("refresh", target)) + .label("Rafraichir") + .style(ButtonStyle::Secondary), + ])] +} + +fn panel_embed(target: RoleMenuTarget) -> CreateEmbed { + CreateEmbed::new() + .title("RoleMenu") + .description("Panneau interactif pour configurer un menu de roles.") + .field("Message cible", target.message_id.to_string(), true) + .field("Canal", format!("<#{}>", target.channel_id), true) + .timestamp(Utc::now()) +} + +fn default_menu_embed(color: u32) -> CreateEmbed { + CreateEmbed::new() + .title("Menu de roles") + .description("Cliquez sur les boutons ci-dessous pour recevoir ou retirer un role.") + .color(color) + .timestamp(Utc::now()) +} + +fn collect_message_buttons(message: &Message) -> Vec { + let mut buttons = Vec::new(); + + for row in &message.components { + for component in &row.components { + if let ActionRowComponent::Button(button) = component { + buttons.push(CreateButton::from(button.clone())); + } + } + } + + buttons +} + +fn message_has_role_button(message: &Message, role_id: RoleId) -> bool { + let wanted = format!("{}:{}", ROLEMENU_TOGGLE_PREFIX, role_id.get()); + + for row in &message.components { + for component in &row.components { + if let ActionRowComponent::Button(button) = component { + if let ButtonKind::NonLink { + custom_id, .. + } = &button.data + { + if custom_id == &wanted { + return true; + } + } + } + } + } + + false +} + +fn button_rows(buttons: &[CreateButton]) -> Vec { + buttons + .chunks(5) + .map(|chunk| CreateActionRow::Buttons(chunk.to_vec())) + .collect() +} + +fn parse_message_id_arg(raw: &str) -> Option { + raw.trim().parse::().ok().map(MessageId::new) +} + +async fn fetch_target_message(ctx: &Context, target: RoleMenuTarget) -> Option { + ChannelId::new(target.channel_id) + .message(&ctx.http, MessageId::new(target.message_id)) + .await + .ok() +} + +async fn respond_component_ephemeral(ctx: &Context, component: &ComponentInteraction, text: &str) { + let _ = component + .create_response( + &ctx.http, + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .content(text) + .ephemeral(true), + ), + ) + .await; +} + +async fn respond_modal_ephemeral(ctx: &Context, modal: &ModalInteraction, text: &str) { + let _ = modal + .create_response( + &ctx.http, + CreateInteractionResponse::Message( + CreateInteractionResponseMessage::new() + .content(text) + .ephemeral(true), + ), + ) + .await; +} + +pub async fn handle_rolemenu(ctx: &Context, msg: &Message, args: &[&str]) { + let Some(_guild_id) = msg.guild_id else { + return; + }; + + let color = theme_color(ctx).await; + + let target_message = if let Some(raw_id) = args.first() { + let Some(message_id) = parse_message_id_arg(raw_id) else { + let _ = msg + .channel_id + .send_message( + &ctx.http, + CreateMessage::new().embed( + CreateEmbed::new() + .title("RoleMenu") + .description("ID invalide. Utilisation: +rolemenu [message_id]") + .color(color), + ), + ) + .await; + return; + }; + + let Ok(existing) = msg.channel_id.message(&ctx.http, message_id).await else { + let _ = msg + .channel_id + .send_message( + &ctx.http, + CreateMessage::new().embed( + CreateEmbed::new() + .title("RoleMenu") + .description("Message introuvable dans ce salon.") + .color(color), + ), + ) + .await; + return; + }; + + existing + } else { + let created = msg + .channel_id + .send_message( + &ctx.http, + CreateMessage::new().embed(default_menu_embed(color)), + ) + .await; + + let Ok(created) = created else { + return; + }; + + created + }; + + let target = RoleMenuTarget { + owner_id: msg.author.id.get(), + channel_id: msg.channel_id.get(), + message_id: target_message.id.get(), + }; + + let _ = msg + .channel_id + .send_message( + &ctx.http, + CreateMessage::new() + .embed(panel_embed(target).color(color)) + .components(panel_components(target)), + ) + .await; +} + +pub async fn handle_component_interaction(ctx: &Context, component: &ComponentInteraction) -> bool { + if let Some(role_id) = parse_toggle_role_id(&component.data.custom_id) { + let Some(guild_id) = component.guild_id else { + respond_component_ephemeral(ctx, component, "Interaction indisponible hors serveur.") + .await; + return true; + }; + + let Ok(member) = guild_id.member(&ctx.http, component.user.id).await else { + respond_component_ephemeral(ctx, component, "Membre introuvable.").await; + return true; + }; + + let has_role = member.roles.contains(&role_id); + let updated = if has_role { + member.remove_role(&ctx.http, role_id).await.is_ok() + } else { + member.add_role(&ctx.http, role_id).await.is_ok() + }; + + let text = if updated { + if has_role { + format!("Role <@&{}> retire.", role_id.get()) + } else { + format!("Role <@&{}> ajoute.", role_id.get()) + } + } else { + "Impossible de mettre a jour ce role (permissions insuffisantes ?).".to_string() + }; + + respond_component_ephemeral(ctx, component, &text).await; + return true; + } + + if !component.data.custom_id.starts_with(ROLEMENU_PANEL_PREFIX) { + return false; + } + + let Some((action, target)) = parse_panel_custom_id(&component.data.custom_id) else { + return false; + }; + + if component.user.id.get() != target.owner_id { + respond_component_ephemeral(ctx, component, "Seul l'auteur du panneau peut l'utiliser.") + .await; + return true; + } + + if action == "refresh" { + let color = theme_color(ctx).await; + let _ = component + .create_response( + &ctx.http, + CreateInteractionResponse::UpdateMessage( + CreateInteractionResponseMessage::new() + .embed(panel_embed(target).color(color)) + .components(panel_components(target)), + ), + ) + .await; + return true; + } + + if action == "embed" { + let modal = CreateModal::new(modal_custom_id("embed", target), "Configurer l'embed") + .components(vec![ + CreateActionRow::InputText( + CreateInputText::new(InputTextStyle::Short, "Titre", "title") + .required(false) + .max_length(100), + ), + CreateActionRow::InputText( + CreateInputText::new(InputTextStyle::Paragraph, "Description", "description") + .required(false) + .max_length(2000), + ), + CreateActionRow::InputText( + CreateInputText::new(InputTextStyle::Short, "Couleur HEX", "color") + .required(false) + .placeholder("#5865F2"), + ), + ]); + + let _ = component + .create_response(&ctx.http, CreateInteractionResponse::Modal(modal)) + .await; + return true; + } + + if action == "addrole" { + let modal = + CreateModal::new(modal_custom_id("addrole", target), "Ajouter un bouton role") + .components(vec![ + CreateActionRow::InputText( + CreateInputText::new(InputTextStyle::Short, "Label du bouton", "label") + .required(true) + .max_length(80), + ), + CreateActionRow::InputText( + CreateInputText::new(InputTextStyle::Short, "Role ID ou mention", "role_id") + .required(true), + ), + CreateActionRow::InputText( + CreateInputText::new(InputTextStyle::Short, "Style", "style") + .required(false) + .placeholder("secondary | primary | success | danger"), + ), + ]); + + let _ = component + .create_response(&ctx.http, CreateInteractionResponse::Modal(modal)) + .await; + return true; + } + + false +} + +pub async fn handle_modal_interaction(ctx: &Context, modal: &ModalInteraction) -> bool { + if !modal.data.custom_id.starts_with(ROLEMENU_MODAL_PREFIX) { + return false; + } + + let Some((action, target)) = parse_modal_custom_id(&modal.data.custom_id) else { + return false; + }; + + if modal.user.id.get() != target.owner_id { + respond_modal_ephemeral(ctx, modal, "Seul l'auteur du panneau peut soumettre ce formulaire.") + .await; + return true; + } + + if action == "embed" { + let Some(target_message) = fetch_target_message(ctx, target).await else { + respond_modal_ephemeral(ctx, modal, "Message cible introuvable.").await; + return true; + }; + + let title = modal_value(modal, "title") + .unwrap_or_default() + .trim() + .to_string(); + let description = modal_value(modal, "description") + .unwrap_or_default() + .trim() + .to_string(); + let color_input = modal_value(modal, "color").unwrap_or_default(); + + let current_embed = target_message.embeds.first(); + let final_title = if title.is_empty() { + current_embed + .and_then(|embed| embed.title.clone()) + .unwrap_or_else(|| "Menu de roles".to_string()) + } else { + title + }; + + let final_description = if description.is_empty() { + current_embed + .and_then(|embed| embed.description.clone()) + .unwrap_or_else(|| { + "Cliquez sur les boutons ci-dessous pour recevoir ou retirer un role." + .to_string() + }) + } else { + description + }; + + let fallback_color = theme_color(ctx).await; + let final_color = parse_hex_color(&color_input) + .or_else(|| current_embed.and_then(|embed| embed.colour.map(|c| c.0))) + .unwrap_or(fallback_color); + + let edited = ChannelId::new(target.channel_id) + .edit_message( + &ctx.http, + MessageId::new(target.message_id), + EditMessage::new().embed( + CreateEmbed::new() + .title(final_title) + .description(final_description) + .color(final_color) + .timestamp(Utc::now()), + ), + ) + .await + .is_ok(); + + if edited { + respond_modal_ephemeral(ctx, modal, "Embed du rolemenu mis a jour.").await; + } else { + respond_modal_ephemeral(ctx, modal, "Impossible de mettre a jour l'embed cible.") + .await; + } + + return true; + } + + if action == "addrole" { + let Some(target_message) = fetch_target_message(ctx, target).await else { + respond_modal_ephemeral(ctx, modal, "Message cible introuvable.").await; + return true; + }; + + let label = modal_value(modal, "label") + .unwrap_or_default() + .trim() + .to_string(); + if label.is_empty() { + respond_modal_ephemeral(ctx, modal, "Label invalide.").await; + return true; + } + + let raw_role = modal_value(modal, "role_id").unwrap_or_default(); + let Some(role_id) = parse_role_id_input(&raw_role) else { + respond_modal_ephemeral(ctx, modal, "Role invalide. Fournis un ID ou une mention.") + .await; + return true; + }; + + if message_has_role_button(&target_message, role_id) { + respond_modal_ephemeral(ctx, modal, "Ce role est deja present dans le menu.").await; + return true; + } + + let mut buttons = collect_message_buttons(&target_message); + if buttons.len() >= 25 { + respond_modal_ephemeral(ctx, modal, "Limite de 25 boutons atteinte.").await; + return true; + } + + let style_raw = modal_value(modal, "style").unwrap_or_default(); + let style = parse_button_style(&style_raw); + + buttons.push( + CreateButton::new(format!("{}:{}", ROLEMENU_TOGGLE_PREFIX, role_id.get())) + .label(label) + .style(style), + ); + + let edited = ChannelId::new(target.channel_id) + .edit_message( + &ctx.http, + MessageId::new(target.message_id), + EditMessage::new().components(button_rows(&buttons)), + ) + .await + .is_ok(); + + if edited { + respond_modal_ephemeral(ctx, modal, "Bouton role ajoute au menu.").await; + } else { + respond_modal_ephemeral(ctx, modal, "Impossible d'ajouter le bouton role.").await; + } + + return true; + } + + false +} + +pub struct RolemenuCommand; +pub static COMMAND_DESCRIPTOR: RolemenuCommand = RolemenuCommand; + +impl crate::commands::command_contract::CommandSpec for RolemenuCommand { + fn metadata(&self) -> crate::commands::command_contract::CommandMetadata { + crate::commands::command_contract::CommandMetadata { + name: "rolemenu", + category: "roles", + params: "[message_id]", + summary: "Cree ou modifie un menu de roles", + description: "Ouvre un panneau interactif (boutons + modales) pour construire un embed de roles et des boutons auto-roles.", + examples: &["+rolemenu", "+rolemenu 123456789012345678", "+help rolemenu"], + default_aliases: &["rmenu"], + default_permission: 8, + } + } +} \ No newline at end of file diff --git a/src/commands/salons_vocal/slowmode.rs b/src/commands/salons_vocal/slowmode.rs new file mode 100644 index 0000000..577b1ec --- /dev/null +++ b/src/commands/salons_vocal/slowmode.rs @@ -0,0 +1,126 @@ +use serenity::builder::{CreateEmbed, EditChannel}; +use serenity::model::prelude::*; +use serenity::prelude::*; + +use crate::commands::common::{parse_channel_id, send_embed, theme_color}; +use crate::commands::moderation_sanction_helpers::duration_from_input; + +const SLOWMODE_MAX_SECONDS: u64 = 6 * 60 * 60; + +fn parse_slowmode_seconds(raw: &str) -> Option { + let normalized = raw.trim().to_lowercase(); + if normalized.is_empty() { + return None; + } + + if matches!(normalized.as_str(), "off" | "none" | "disable" | "disabled") { + return Some(0); + } + + if let Ok(seconds) = normalized.parse::() { + return Some(seconds); + } + + duration_from_input(&normalized).map(|duration| duration.as_secs()) +} + +pub async fn handle_slowmode(ctx: &Context, msg: &Message, args: &[&str]) { + let Some(_guild_id) = msg.guild_id else { + return; + }; + + let Some(raw_duration) = args.first() else { + send_embed( + ctx, + msg, + CreateEmbed::new() + .title("Slowmode") + .description("Utilisation: +slowmode [salon]") + .color(theme_color(ctx).await), + ) + .await; + return; + }; + + let Some(seconds) = parse_slowmode_seconds(raw_duration) else { + send_embed( + ctx, + msg, + CreateEmbed::new() + .title("Slowmode") + .description("Duree invalide. Exemples: 10s, 2m, 1h, off") + .color(theme_color(ctx).await), + ) + .await; + return; + }; + + if seconds > SLOWMODE_MAX_SECONDS { + send_embed( + ctx, + msg, + CreateEmbed::new() + .title("Slowmode") + .description("La duree maximale du mode lent est 6h.") + .color(theme_color(ctx).await), + ) + .await; + return; + } + + let target = args + .get(1) + .and_then(|raw| parse_channel_id(raw)) + .unwrap_or(msg.channel_id); + + let applied = target + .edit( + &ctx.http, + EditChannel::new().rate_limit_per_user(seconds as u16), + ) + .await + .is_ok(); + + let description = if applied { + if seconds == 0 { + format!("Mode lent desactive sur <#{}>.", target.get()) + } else { + format!("Mode lent de {}s applique sur <#{}>.", seconds, target.get()) + } + } else { + "Echec de mise a jour du mode lent (verifie le type de salon et les permissions)." + .to_string() + }; + + send_embed( + ctx, + msg, + CreateEmbed::new() + .title("Slowmode") + .description(description) + .color(theme_color(ctx).await), + ) + .await; +} + +pub struct SlowmodeCommand; +pub static COMMAND_DESCRIPTOR: SlowmodeCommand = SlowmodeCommand; + +impl crate::commands::command_contract::CommandSpec for SlowmodeCommand { + fn metadata(&self) -> crate::commands::command_contract::CommandMetadata { + crate::commands::command_contract::CommandMetadata { + name: "slowmode", + category: "salons_vocal", + params: " [salon]", + summary: "Change le mode lent d'un salon", + description: "Modifie la duree du mode lent sur un salon texte (maximum 6 heures).", + examples: &[ + "+slowmode 10s", + "+slowmode 2m #general", + "+slowmode off", + ], + default_aliases: &["sm"], + default_permission: 8, + } + } +} \ No newline at end of file diff --git a/src/events/interaction_create_event.rs b/src/events/interaction_create_event.rs index 1d94bed..5de67c5 100644 --- a/src/events/interaction_create_event.rs +++ b/src/events/interaction_create_event.rs @@ -2,7 +2,8 @@ use serenity::model::prelude::*; use serenity::prelude::*; use crate::commands::{ - advanced_tools, boostembed, help, helpsetting, mp, perms_service, suggestion, tempvoc, ticket, + advanced_tools, boostembed, help, helpsetting, mp, perms_service, rolemenu, suggestion, + tempvoc, ticket, }; pub async fn handle_interaction_create(ctx: &Context, interaction: &Interaction) { @@ -29,6 +30,10 @@ pub async fn handle_interaction_create(ctx: &Context, interaction: &Interaction) return; } + if rolemenu::handle_component_interaction(ctx, component).await { + return; + } + if help::handle_help_component(ctx, component).await { return; } @@ -66,6 +71,10 @@ pub async fn handle_interaction_create(ctx: &Context, interaction: &Interaction) return; } + if rolemenu::handle_modal_interaction(ctx, modal).await { + return; + } + let _ = advanced_tools::handle_modal_interaction(ctx, modal).await; } } diff --git a/src/events/message_event.rs b/src/events/message_event.rs index 8332712..644770f 100644 --- a/src/events/message_event.rs +++ b/src/events/message_event.rs @@ -14,8 +14,9 @@ use crate::commands::{ invisible, invite, join, kick, leave, leave_settings, listen, loading, lock, lockall, mainprefix, massiverole, member, messagelog, modlog, mp, mute, mutelist, newsticker, nolog, online, owner, perms, pic, ping, playto, prefix, raidlog, rename, renew, reroll, role, rolelog, - rolemembers, sanctions, say, server, serverinfo, set, set_boostembed, set_modlogs, shadowbot, - showpics, snipe, stream, suggestion, sync, tempban, tempcmute, tempmute, temprole, tempvoc, + rolemembers, rolemenu, sanctions, say, server, serverinfo, set, set_boostembed, set_modlogs, + shadowbot, showpics, slowmode, snipe, stream, suggestion, sync, tempban, tempcmute, tempmute, + temprole, tempvoc, tempvoc_cmd, theme, ticket, ticket_member, tickets, unban, unbanall, unbl, uncmute, unhide, unhideall, unlock, unlockall, unmassiverole, unmute, unmuteall, unowner, untemprole, user, viewlogs, vocinfo, voicekick, voicelog, voicemove, warn, watch, @@ -173,6 +174,7 @@ pub async fn handle_message(ctx: &Context, msg: &Message) { "serverinfo" => serverinfo::handle_serverinfo(ctx, msg, &args).await, "vocinfo" => vocinfo::handle_vocinfo(ctx, msg, &args).await, "role" => role::handle_role(ctx, msg, &args).await, + "rolemenu" => rolemenu::handle_rolemenu(ctx, msg, &args).await, "channel" => channel::handle_channel(ctx, msg, &args).await, "user" => user::handle_user(ctx, msg, &args).await, "member" => member::handle_member(ctx, msg, &args).await, @@ -270,6 +272,11 @@ pub async fn handle_message(ctx: &Context, msg: &Message) { banlist::handle_banlist(ctx, msg).await; crate::commands::logs_service::log_moderation_command(ctx, msg, "banlist", &args).await; } + "slowmode" => { + slowmode::handle_slowmode(ctx, msg, &args).await; + crate::commands::logs_service::log_moderation_command(ctx, msg, "slowmode", &args) + .await; + } "lock" => { lock::handle_lock(ctx, msg, &args).await; crate::commands::logs_service::log_moderation_command(ctx, msg, "lock", &args).await; diff --git a/src/permissions.rs b/src/permissions.rs index 213061e..4de316e 100644 --- a/src/permissions.rs +++ b/src/permissions.rs @@ -232,6 +232,7 @@ pub fn all_command_keys() -> Vec { "botadmins", "boosters", "rolemembers", + "rolemenu", "serverinfo", "vocinfo", "role", @@ -265,6 +266,7 @@ pub fn all_command_keys() -> Vec { "tempban", "unban", "banlist", + "slowmode", "lock", "unlock", "lockall", @@ -440,6 +442,7 @@ pub fn default_permission(command_key: &str) -> u8 { | "tempban" | "unban" | "banlist" + | "slowmode" | "lock" | "unlock" | "lockall" @@ -451,6 +454,7 @@ pub fn default_permission(command_key: &str) -> u8 { | "addrole" | "delrole" | "derank" + | "rolemenu" | "modlog" | "messagelog" | "voicelog"