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 {
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()
},

View File

@@ -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<UserMarker>,
interaction: &Interaction,
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_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,9 +86,12 @@ 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),
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
@@ -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");
tracing::error!("TODO: successfully left the call");

View File

@@ -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<ApplicationMarker>,
pub discord_bot_owner_user_id: Id<UserMarker>,
pub discord_client: Arc<twilight_http::Client>,
pub discord_user_id: Id<UserMarker>,
pub songbird: Arc<Songbird>,

View File

@@ -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,