From 7d3a309d2bbc74c80a8ee88abb4997267e1ca2fe Mon Sep 17 00:00:00 2001 From: Jacob Date: Wed, 8 Apr 2026 23:30:26 -0400 Subject: [PATCH] feat: check if the leave command was invoked by the bot owner before leaving --- src/command/join.rs | 2 +- src/command/leave.rs | 98 +++++++++++++++++++++++++++++++------------- src/command/mod.rs | 6 ++- src/main.rs | 1 + 4 files changed, 76 insertions(+), 31 deletions(-) diff --git a/src/command/join.rs b/src/command/join.rs index f8e32e4..49be9ee 100644 --- a/src/command/join.rs +++ b/src/command/join.rs @@ -66,7 +66,7 @@ fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Emb match error { GetGuildAndVoiceChannelIdError::NotInGuild => { EmbedBuilder::new().title("Use this in a server").description("This bot can't find a VC to join if the command is used outside of a server (you might've used it in a DM?).").validate().unwrap().build() - } + }, GetGuildAndVoiceChannelIdError::NoUser => { EmbedBuilder::new().title("Not invoked by a user").description("This command works by joining the same VC as the user, but this bot didn't receive any user data. So did no user invoke it?! (This error should be impossible!)").validate().unwrap().build() }, diff --git a/src/command/leave.rs b/src/command/leave.rs index 5c2ecaf..aa64e86 100644 --- a/src/command/leave.rs +++ b/src/command/leave.rs @@ -34,6 +34,9 @@ pub enum GetGuildAndVoiceChannelIdError { /// this command was not used inside a guild (Discord server) NotInGuild, + /// there is no user who invoked this command + NoUser, + /// there are no voice chats in this guild NoVCsInGuild, @@ -42,11 +45,17 @@ pub enum GetGuildAndVoiceChannelIdError { } #[tracing::instrument] -pub fn get_guild_and_voice_channel_id( +pub fn get_user_and_guild_and_voice_channel_id( bot_user_id: Id, interaction: &Interaction, vcs: &VCs, -) -> Result<(Id, Id), GetGuildAndVoiceChannelIdError> { +) -> Result<(Id, Id, Id), GetGuildAndVoiceChannelIdError> { + let user_id = interaction + .member + .as_ref() + .and_then(|member| member.user.as_ref().map(|user| user.id)) + .context(NoUserSnafu)?; + let guild_id = interaction.guild_id.context(NotInGuildSnafu)?; let guild_vcs = vcs.get(&guild_id).context(NoVCsInGuildSnafu)?; @@ -55,14 +64,17 @@ pub fn get_guild_and_voice_channel_id( .get_left_for(&bot_user_id) .context(BotNotInVCSnafu)?; - Ok((guild_id, voice_channel_id)) + Ok((user_id, guild_id, voice_channel_id)) } fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Embed { match error { GetGuildAndVoiceChannelIdError::NotInGuild => { EmbedBuilder::new().title("Use this in a server").description("This bot can't tell which VC to leave if the command is used outside of a server (you might've used it in a DM?).").validate().unwrap().build() - } + }, + GetGuildAndVoiceChannelIdError::NoUser => { + EmbedBuilder::new().title("Not invoked by a user").description("This command works by joining the same VC as the user, but this bot didn't receive any user data. So did no user invoke it?! (This error should be impossible!)").validate().unwrap().build() + }, GetGuildAndVoiceChannelIdError::NoVCsInGuild => { EmbedBuilder::new().title("No VCs in this server").description("This bot can't leave VC because there aren't any in this server right now (therefore the bot must not be in any).").validate().unwrap().build() }, @@ -74,32 +86,60 @@ fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Emb #[tracing::instrument] pub async fn handle(state: State, interaction: Interaction) { - let (guild_id, voice_channel_id) = - match get_guild_and_voice_channel_id(state.discord_user_id, &interaction, &state.vcs) { - Ok((guild_id, voice_channel_id)) => (guild_id, voice_channel_id), - Err(error) => { - state - .discord_client - .interaction(state.discord_application_id) - .create_response( - interaction.id, - &interaction.token, - &InteractionResponse { - kind: InteractionResponseType::ChannelMessageWithSource, - data: Some( - InteractionResponseDataBuilder::new() - .embeds([get_guild_and_vc_error_to_embed(error)]) - .flags(MessageFlags::EPHEMERAL) - .build(), - ), - }, - ) - .await - .expect("TODO"); + let (user_id, guild_id, voice_channel_id) = match get_user_and_guild_and_voice_channel_id( + state.discord_user_id, + &interaction, + &state.vcs, + ) { + Ok((user_id, guild_id, voice_channel_id)) => (user_id, guild_id, voice_channel_id), + Err(error) => { + state + .discord_client + .interaction(state.discord_application_id) + .create_response( + interaction.id, + &interaction.token, + &InteractionResponse { + kind: InteractionResponseType::ChannelMessageWithSource, + data: Some( + InteractionResponseDataBuilder::new() + .embeds([get_guild_and_vc_error_to_embed(error)]) + .flags(MessageFlags::EPHEMERAL) + .build(), + ), + }, + ) + .await + .expect("TODO"); - return; - } - }; + return; + } + }; + + if user_id != state.discord_bot_owner_user_id { + state + .discord_client + .interaction(state.discord_application_id) + .create_response( + interaction.id, + &interaction.token, + &InteractionResponse { + kind: InteractionResponseType::ChannelMessageWithSource, + data: Some( + InteractionResponseDataBuilder::new() + .embeds([ + EmbedBuilder::new().title("No permission to make this bot leave").description("Only the owner of the bot is allowed to make the bot leave RIGHT NOW. You might be looking for the opt out command.").validate().unwrap().build() + ]) + .flags(MessageFlags::EPHEMERAL) + .build(), + ), + }, + ) + .await + .expect("TODO"); + + return; + } state.songbird.leave(guild_id).await.expect("TODO"); diff --git a/src/command/mod.rs b/src/command/mod.rs index d28c58a..99f0461 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -6,7 +6,10 @@ use songbird::Songbird; use tokio_util::sync::CancellationToken; use twilight_model::{ application::{command::Command, interaction::Interaction}, - id::{Id, marker::{ApplicationMarker, UserMarker}}, + id::{ + Id, + marker::{ApplicationMarker, UserMarker}, + }, }; use crate::VCs; @@ -19,6 +22,7 @@ mod opt_out; pub struct State { pub cancellation_token: CancellationToken, pub discord_application_id: Id, + pub discord_bot_owner_user_id: Id, pub discord_client: Arc, pub discord_user_id: Id, pub songbird: Arc, diff --git a/src/main.rs b/src/main.rs index f072a29..0f5148e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -201,6 +201,7 @@ async fn main() -> Result<(), MainError> { let state = State { cancellation_token: cancellation_token.clone(), discord_application_id, + discord_bot_owner_user_id: bot_owner, discord_client, discord_user_id, songbird,