feat: check if the leave command was invoked by the bot owner before leaving

This commit is contained in:
2026-04-08 23:30:26 -04:00
parent d2511f7a55
commit 7d3a309d2b
4 changed files with 76 additions and 31 deletions

View File

@@ -66,7 +66,7 @@ fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Emb
match error { match error {
GetGuildAndVoiceChannelIdError::NotInGuild => { 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() 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 => { 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() 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()
}, },

View File

@@ -34,6 +34,9 @@ pub enum GetGuildAndVoiceChannelIdError {
/// this command was not used inside a guild (Discord server) /// this command was not used inside a guild (Discord server)
NotInGuild, NotInGuild,
/// there is no user who invoked this command
NoUser,
/// there are no voice chats in this guild /// there are no voice chats in this guild
NoVCsInGuild, NoVCsInGuild,
@@ -42,11 +45,17 @@ pub enum GetGuildAndVoiceChannelIdError {
} }
#[tracing::instrument] #[tracing::instrument]
pub fn get_guild_and_voice_channel_id( pub fn get_user_and_guild_and_voice_channel_id(
bot_user_id: Id<UserMarker>, bot_user_id: Id<UserMarker>,
interaction: &Interaction, interaction: &Interaction,
vcs: &VCs, vcs: &VCs,
) -> Result<(Id<GuildMarker>, Id<ChannelMarker>), GetGuildAndVoiceChannelIdError> { ) -> Result<(Id<UserMarker>, Id<GuildMarker>, Id<ChannelMarker>), 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_id = interaction.guild_id.context(NotInGuildSnafu)?;
let guild_vcs = vcs.get(&guild_id).context(NoVCsInGuildSnafu)?; 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) .get_left_for(&bot_user_id)
.context(BotNotInVCSnafu)?; .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 { fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Embed {
match error { match error {
GetGuildAndVoiceChannelIdError::NotInGuild => { 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() 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 => { 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() 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,9 +86,12 @@ fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Emb
#[tracing::instrument] #[tracing::instrument]
pub async fn handle(state: State, interaction: Interaction) { pub async fn handle(state: State, interaction: Interaction) {
let (guild_id, voice_channel_id) = let (user_id, guild_id, voice_channel_id) = match get_user_and_guild_and_voice_channel_id(
match get_guild_and_voice_channel_id(state.discord_user_id, &interaction, &state.vcs) { state.discord_user_id,
Ok((guild_id, voice_channel_id)) => (guild_id, voice_channel_id), &interaction,
&state.vcs,
) {
Ok((user_id, guild_id, voice_channel_id)) => (user_id, guild_id, voice_channel_id),
Err(error) => { Err(error) => {
state state
.discord_client .discord_client
@@ -101,6 +116,31 @@ pub async fn handle(state: State, interaction: Interaction) {
} }
}; };
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"); state.songbird.leave(guild_id).await.expect("TODO");
tracing::error!("TODO: successfully left the call"); tracing::error!("TODO: successfully left the call");

View File

@@ -6,7 +6,10 @@ use songbird::Songbird;
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use twilight_model::{ use twilight_model::{
application::{command::Command, interaction::Interaction}, application::{command::Command, interaction::Interaction},
id::{Id, marker::{ApplicationMarker, UserMarker}}, id::{
Id,
marker::{ApplicationMarker, UserMarker},
},
}; };
use crate::VCs; use crate::VCs;
@@ -19,6 +22,7 @@ mod opt_out;
pub struct State { pub struct State {
pub cancellation_token: CancellationToken, pub cancellation_token: CancellationToken,
pub discord_application_id: Id<ApplicationMarker>, pub discord_application_id: Id<ApplicationMarker>,
pub discord_bot_owner_user_id: Id<UserMarker>,
pub discord_client: Arc<twilight_http::Client>, pub discord_client: Arc<twilight_http::Client>,
pub discord_user_id: Id<UserMarker>, pub discord_user_id: Id<UserMarker>,
pub songbird: Arc<Songbird>, pub songbird: Arc<Songbird>,

View File

@@ -201,6 +201,7 @@ async fn main() -> Result<(), MainError> {
let state = State { let state = State {
cancellation_token: cancellation_token.clone(), cancellation_token: cancellation_token.clone(),
discord_application_id, discord_application_id,
discord_bot_owner_user_id: bot_owner,
discord_client, discord_client,
discord_user_id, discord_user_id,
songbird, songbird,