From f5d652bd7a7cbd5c49fe308b14f44de8ea5eedcb Mon Sep 17 00:00:00 2001 From: Akinator31 <99099121+Akinator31@users.noreply.github.com> Date: Wed, 14 Oct 2026 16:40:02 +0200 Subject: [PATCH 1/2] feat(commands): answer commands run outside the staff guild ephemerally --- .../add_reminder/slash_command/add_reminder.rs | 2 +- .../add_staff/slash_command/add_staff.rs | 2 +- .../src/commands/alert/slash_command/alert.rs | 2 +- .../commands/baninfo/slash_command/baninfo.rs | 2 +- .../commands/category/slash_command/category.rs | 2 +- .../src/commands/close/slash_command/close.rs | 2 +- .../src/commands/edit/slash_command/edit.rs | 2 +- .../force_close/slash_command/force_close.rs | 2 +- .../src/commands/help/slash_command/help.rs | 2 +- .../rustmail/src/commands/id/slash_command/id.rs | 2 +- .../src/commands/logs/slash_command/logs.rs | 2 +- .../move_thread/slash_command/move_thread.rs | 2 +- .../new_thread/slash_command/new_thread.rs | 2 +- .../src/commands/ping/slash_command/ping.rs | 2 +- .../commands/recover/slash_command/recover.rs | 2 +- .../commands/release/slash_command/release.rs | 2 +- .../slash_command/reminder_subscription.rs | 2 +- .../slash_command/remove_reminder.rs | 2 +- .../remove_staff/slash_command/remove_staff.rs | 2 +- .../src/commands/rename/slash_command/rename.rs | 2 +- .../src/commands/reply/slash_command/reply.rs | 2 +- .../commands/snippet/slash_command/snippet.rs | 2 +- .../src/commands/status/slash_command/status.rs | 2 +- .../src/commands/take/slash_command/take.rs | 2 +- .../commands/version/slash_command/version.rs | 2 +- .../src/handlers/guild_interaction_handler.rs | 2 +- .../rustmail/src/utils/command/defer_response.rs | 16 +++++++++++++++- 27 files changed, 41 insertions(+), 27 deletions(-) diff --git a/crates/rustmail/src/commands/add_reminder/slash_command/add_reminder.rs b/crates/rustmail/src/commands/add_reminder/slash_command/add_reminder.rs index a9135b5..fdc3e3d 100644 --- a/crates/rustmail/src/commands/add_reminder/slash_command/add_reminder.rs +++ b/crates/rustmail/src/commands/add_reminder/slash_command/add_reminder.rs @@ -111,7 +111,7 @@ impl RegistrableCommand for AddReminderCommand { .as_ref() .ok_or_else(database_connection_failed)?; - let _ = defer_response(&ctx, &command).await; + let _ = defer_response(&ctx, &command, &config).await; let mut time: Option = None; let mut content: Option = None; diff --git a/crates/rustmail/src/commands/add_staff/slash_command/add_staff.rs b/crates/rustmail/src/commands/add_staff/slash_command/add_staff.rs index cbd2e68..ae8d9cd 100644 --- a/crates/rustmail/src/commands/add_staff/slash_command/add_staff.rs +++ b/crates/rustmail/src/commands/add_staff/slash_command/add_staff.rs @@ -78,7 +78,7 @@ impl RegistrableCommand for AddStaffCommand { .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; if !thread_exists_by_channel(command.channel_id, pool).await { return Err(ModmailError::Thread(ThreadError::NotAThreadChannel)); diff --git a/crates/rustmail/src/commands/alert/slash_command/alert.rs b/crates/rustmail/src/commands/alert/slash_command/alert.rs index ac0eb4e..61f58ee 100644 --- a/crates/rustmail/src/commands/alert/slash_command/alert.rs +++ b/crates/rustmail/src/commands/alert/slash_command/alert.rs @@ -76,7 +76,7 @@ impl RegistrableCommand for AlertCommand { .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; let user_id = get_thread_user_id_from_command(&ctx, &command, &config, pool).await?; diff --git a/crates/rustmail/src/commands/baninfo/slash_command/baninfo.rs b/crates/rustmail/src/commands/baninfo/slash_command/baninfo.rs index 4ede5bf..e65fd17 100644 --- a/crates/rustmail/src/commands/baninfo/slash_command/baninfo.rs +++ b/crates/rustmail/src/commands/baninfo/slash_command/baninfo.rs @@ -78,7 +78,7 @@ impl RegistrableCommand for BaninfoCommand { .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; let mut query: Option = None; for option in &command.data.options { diff --git a/crates/rustmail/src/commands/category/slash_command/category.rs b/crates/rustmail/src/commands/category/slash_command/category.rs index 6cd3fd2..19d54be 100644 --- a/crates/rustmail/src/commands/category/slash_command/category.rs +++ b/crates/rustmail/src/commands/category/slash_command/category.rs @@ -288,7 +288,7 @@ impl RegistrableCommand for CategoryCommand { .db_pool .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; let subcommand = match command.data.options.first() { Some(s) => s, diff --git a/crates/rustmail/src/commands/close/slash_command/close.rs b/crates/rustmail/src/commands/close/slash_command/close.rs index d5524ca..40808f3 100644 --- a/crates/rustmail/src/commands/close/slash_command/close.rs +++ b/crates/rustmail/src/commands/close/slash_command/close.rs @@ -111,7 +111,7 @@ impl RegistrableCommand for CloseCommand { .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; let mut time_before_close: Option = None; let mut silent: bool = false; diff --git a/crates/rustmail/src/commands/edit/slash_command/edit.rs b/crates/rustmail/src/commands/edit/slash_command/edit.rs index 821a9ea..675fc4a 100644 --- a/crates/rustmail/src/commands/edit/slash_command/edit.rs +++ b/crates/rustmail/src/commands/edit/slash_command/edit.rs @@ -99,7 +99,7 @@ impl RegistrableCommand for EditCommand { .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; let mut msg_id: i64 = 0; let mut new_content: String = String::new(); diff --git a/crates/rustmail/src/commands/force_close/slash_command/force_close.rs b/crates/rustmail/src/commands/force_close/slash_command/force_close.rs index 6f5bec9..a42fbd5 100644 --- a/crates/rustmail/src/commands/force_close/slash_command/force_close.rs +++ b/crates/rustmail/src/commands/force_close/slash_command/force_close.rs @@ -59,7 +59,7 @@ impl RegistrableCommand for ForceCloseCommand { .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; if !is_a_ticket_channel(command.channel_id, db_pool).await { match command.channel_id.to_channel(&ctx.http).await { diff --git a/crates/rustmail/src/commands/help/slash_command/help.rs b/crates/rustmail/src/commands/help/slash_command/help.rs index e51489e..f78b831 100644 --- a/crates/rustmail/src/commands/help/slash_command/help.rs +++ b/crates/rustmail/src/commands/help/slash_command/help.rs @@ -69,7 +69,7 @@ impl RegistrableCommand for HelpCommand { let config = config.clone(); Box::pin(async move { - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; let mut command_name: Option = None; diff --git a/crates/rustmail/src/commands/id/slash_command/id.rs b/crates/rustmail/src/commands/id/slash_command/id.rs index e4ad07d..09f0c18 100644 --- a/crates/rustmail/src/commands/id/slash_command/id.rs +++ b/crates/rustmail/src/commands/id/slash_command/id.rs @@ -61,7 +61,7 @@ impl RegistrableCommand for IdCommand { } }; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; if !is_a_ticket_channel(command.channel_id, pool).await { return Err(ModmailError::Thread(ThreadError::NotAThreadChannel)); diff --git a/crates/rustmail/src/commands/logs/slash_command/logs.rs b/crates/rustmail/src/commands/logs/slash_command/logs.rs index 29e6b16..4f63b6b 100644 --- a/crates/rustmail/src/commands/logs/slash_command/logs.rs +++ b/crates/rustmail/src/commands/logs/slash_command/logs.rs @@ -70,7 +70,7 @@ impl RegistrableCommand for LogsCommand { None => return Err(ModmailError::Database(DatabaseError::ConnectionFailed)), }; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; let mut user_id: Option = None; diff --git a/crates/rustmail/src/commands/move_thread/slash_command/move_thread.rs b/crates/rustmail/src/commands/move_thread/slash_command/move_thread.rs index 05f1b71..beba393 100644 --- a/crates/rustmail/src/commands/move_thread/slash_command/move_thread.rs +++ b/crates/rustmail/src/commands/move_thread/slash_command/move_thread.rs @@ -82,7 +82,7 @@ impl RegistrableCommand for MoveCommand { } }; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; if get_user_id_from_channel_id(&command.channel_id.to_string(), pool) .await diff --git a/crates/rustmail/src/commands/new_thread/slash_command/new_thread.rs b/crates/rustmail/src/commands/new_thread/slash_command/new_thread.rs index 9e2e931..d358555 100644 --- a/crates/rustmail/src/commands/new_thread/slash_command/new_thread.rs +++ b/crates/rustmail/src/commands/new_thread/slash_command/new_thread.rs @@ -85,7 +85,7 @@ impl RegistrableCommand for NewThreadCommand { .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; let user_id = match command .data diff --git a/crates/rustmail/src/commands/ping/slash_command/ping.rs b/crates/rustmail/src/commands/ping/slash_command/ping.rs index f1fb669..03e085b 100644 --- a/crates/rustmail/src/commands/ping/slash_command/ping.rs +++ b/crates/rustmail/src/commands/ping/slash_command/ping.rs @@ -60,7 +60,7 @@ impl RegistrableCommand for PingCommand { let config = config.clone(); Box::pin(async move { - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; let response = MessageBuilder::system_message(&ctx, &config) .content("...") diff --git a/crates/rustmail/src/commands/recover/slash_command/recover.rs b/crates/rustmail/src/commands/recover/slash_command/recover.rs index f2da684..d4f1f5b 100644 --- a/crates/rustmail/src/commands/recover/slash_command/recover.rs +++ b/crates/rustmail/src/commands/recover/slash_command/recover.rs @@ -59,7 +59,7 @@ impl RegistrableCommand for RecoverCommand { .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; let mut params = HashMap::new(); params.insert("user".to_string(), command.user.name.clone()); diff --git a/crates/rustmail/src/commands/release/slash_command/release.rs b/crates/rustmail/src/commands/release/slash_command/release.rs index 926c441..6395ff7 100644 --- a/crates/rustmail/src/commands/release/slash_command/release.rs +++ b/crates/rustmail/src/commands/release/slash_command/release.rs @@ -59,7 +59,7 @@ impl RegistrableCommand for ReleaseCommand { .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; if is_a_ticket_channel(command.channel_id, db_pool).await { let thread = match get_thread_by_channel_id( diff --git a/crates/rustmail/src/commands/reminder_subscription/slash_command/reminder_subscription.rs b/crates/rustmail/src/commands/reminder_subscription/slash_command/reminder_subscription.rs index 58eb874..450e380 100644 --- a/crates/rustmail/src/commands/reminder_subscription/slash_command/reminder_subscription.rs +++ b/crates/rustmail/src/commands/reminder_subscription/slash_command/reminder_subscription.rs @@ -97,7 +97,7 @@ impl RegistrableCommand for ReminderSubscriptionCommand { .as_ref() .ok_or_else(database_connection_failed)?; - let _ = defer_response(&ctx, &command).await; + let _ = defer_response(&ctx, &command, &config).await; let mut action: Option = None; let mut role_id: Option = None; diff --git a/crates/rustmail/src/commands/remove_reminder/slash_command/remove_reminder.rs b/crates/rustmail/src/commands/remove_reminder/slash_command/remove_reminder.rs index 81506a4..847e541 100644 --- a/crates/rustmail/src/commands/remove_reminder/slash_command/remove_reminder.rs +++ b/crates/rustmail/src/commands/remove_reminder/slash_command/remove_reminder.rs @@ -75,7 +75,7 @@ impl RegistrableCommand for RemoveReminderCommand { .as_ref() .ok_or_else(database_connection_failed)?; - let _ = defer_response(&ctx, &command).await; + let _ = defer_response(&ctx, &command, &config).await; let mut reminder_id: Option = None; diff --git a/crates/rustmail/src/commands/remove_staff/slash_command/remove_staff.rs b/crates/rustmail/src/commands/remove_staff/slash_command/remove_staff.rs index db0062e..c34590b 100644 --- a/crates/rustmail/src/commands/remove_staff/slash_command/remove_staff.rs +++ b/crates/rustmail/src/commands/remove_staff/slash_command/remove_staff.rs @@ -80,7 +80,7 @@ impl RegistrableCommand for RemoveStaffCommand { .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; if !thread_exists_by_channel(command.channel_id, pool).await { return Err(ModmailError::Thread(ThreadError::NotAThreadChannel)); diff --git a/crates/rustmail/src/commands/rename/slash_command/rename.rs b/crates/rustmail/src/commands/rename/slash_command/rename.rs index 03426a0..2bfc2dc 100644 --- a/crates/rustmail/src/commands/rename/slash_command/rename.rs +++ b/crates/rustmail/src/commands/rename/slash_command/rename.rs @@ -79,7 +79,7 @@ impl RegistrableCommand for RenameCommand { .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; if !is_a_ticket_channel(command.channel_id, db_pool).await { return Err(ModmailError::Thread(ThreadError::NotAThreadChannel)); diff --git a/crates/rustmail/src/commands/reply/slash_command/reply.rs b/crates/rustmail/src/commands/reply/slash_command/reply.rs index 7a36511..9eebae5 100644 --- a/crates/rustmail/src/commands/reply/slash_command/reply.rs +++ b/crates/rustmail/src/commands/reply/slash_command/reply.rs @@ -132,7 +132,7 @@ impl RegistrableCommand for ReplyCommand { .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; let mut content: Option = None; let mut snippet_key: Option = None; diff --git a/crates/rustmail/src/commands/snippet/slash_command/snippet.rs b/crates/rustmail/src/commands/snippet/slash_command/snippet.rs index e452e89..79ed934 100644 --- a/crates/rustmail/src/commands/snippet/slash_command/snippet.rs +++ b/crates/rustmail/src/commands/snippet/slash_command/snippet.rs @@ -232,7 +232,7 @@ impl RegistrableCommand for SnippetCommand { .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; let subcommand = &command.data.options[0]; let subcommand_name = &subcommand.name; diff --git a/crates/rustmail/src/commands/status/slash_command/status.rs b/crates/rustmail/src/commands/status/slash_command/status.rs index 5b461c1..f81118c 100644 --- a/crates/rustmail/src/commands/status/slash_command/status.rs +++ b/crates/rustmail/src/commands/status/slash_command/status.rs @@ -162,7 +162,7 @@ impl RegistrableCommand for StatusCommand { let config = config.clone(); Box::pin(async move { - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; let mode = command.data.options.iter().find_map(|opt| { if opt.name == "mode" diff --git a/crates/rustmail/src/commands/take/slash_command/take.rs b/crates/rustmail/src/commands/take/slash_command/take.rs index 9256613..9bc02e3 100644 --- a/crates/rustmail/src/commands/take/slash_command/take.rs +++ b/crates/rustmail/src/commands/take/slash_command/take.rs @@ -59,7 +59,7 @@ impl RegistrableCommand for TakeCommand { .as_ref() .ok_or_else(database_connection_failed)?; - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; if is_a_ticket_channel(command.channel_id, db_pool).await { let thread = match get_thread_by_channel_id( diff --git a/crates/rustmail/src/commands/version/slash_command/version.rs b/crates/rustmail/src/commands/version/slash_command/version.rs index d6dc542..257a65f 100644 --- a/crates/rustmail/src/commands/version/slash_command/version.rs +++ b/crates/rustmail/src/commands/version/slash_command/version.rs @@ -59,7 +59,7 @@ impl RegistrableCommand for VersionCommand { let config = config.clone(); Box::pin(async move { - defer_response(&ctx, &command).await?; + defer_response(&ctx, &command, &config).await?; let content = build_version_content(&config).await; diff --git a/crates/rustmail/src/handlers/guild_interaction_handler.rs b/crates/rustmail/src/handlers/guild_interaction_handler.rs index b7f19e6..54adec6 100644 --- a/crates/rustmail/src/handlers/guild_interaction_handler.rs +++ b/crates/rustmail/src/handlers/guild_interaction_handler.rs @@ -160,7 +160,7 @@ impl EventHandler for InteractionHandler { ) .await { - defer_response(&ctx, &command).await.ok(); + defer_response(&ctx, &command, &self.config).await.ok(); let _ = MessageBuilder::system_message(&ctx, &self.config) .translated_content( diff --git a/crates/rustmail/src/utils/command/defer_response.rs b/crates/rustmail/src/utils/command/defer_response.rs index 26da84b..5cae341 100644 --- a/crates/rustmail/src/utils/command/defer_response.rs +++ b/crates/rustmail/src/utils/command/defer_response.rs @@ -1,9 +1,23 @@ +use crate::prelude::config::*; use crate::prelude::errors::*; use serenity::all::{ CommandInteraction, Context, CreateInteractionResponse, CreateInteractionResponseMessage, }; -pub async fn defer_response(ctx: &Context, command: &CommandInteraction) -> ModmailResult<()> { +/// Defers a slash command. Outside the staff guild (community guild in dual mode) the response is +/// ephemeral, so only the invoking staff member sees the bot's answer (and any error). +pub async fn defer_response( + ctx: &Context, + command: &CommandInteraction, + config: &Config, +) -> ModmailResult<()> { + let outside_staff_guild = command + .guild_id + .is_some_and(|id| id.get() != config.bot.get_staff_guild_id()); + if outside_staff_guild { + return defer_response_ephemeral(ctx, command).await; + } + command .create_response( &ctx.http, From 937b54b89e0bd2e38c6e01f650e26a4b515752c9 Mon Sep 17 00:00:00 2001 From: Akinator31 <99099121+Akinator31@users.noreply.github.com> Date: Wed, 14 Oct 2026 16:40:29 +0200 Subject: [PATCH 2/2] docs(commands): note ephemeral new_thread replies in the community server --- docs/guides/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/commands.md b/docs/guides/commands.md index 5b462c5..f58dc09 100644 --- a/docs/guides/commands.md +++ b/docs/guides/commands.md @@ -378,7 +378,7 @@ Commands (text and slash) are reserved to staff: - **Dual-server mode**: commands only work in the staff server. Slash commands are not registered in the community server, and text commands sent there are ignored. The one exception is the *New Thread* user context menu (right-click a member → Apps), which is available in the community server too, but only for members of the staff - server. + server. Its reply there is ephemeral ("Only you can see this"), so community members see nothing of it. - **Single-server mode**: staff are the members who can see the inbox category (`inbox_category_id`), which is also who can see the tickets. Other members are ignored by text commands and get a "permission denied" reply to slash commands. To also hide slash commands from them, restrict the bot's commands in *Server Settings → Integrations*.