diff --git a/src/command/join.rs b/src/command/join.rs deleted file mode 100644 index 3b6f936..0000000 --- a/src/command/join.rs +++ /dev/null @@ -1,170 +0,0 @@ -use crate::{VCs, call::join_and_record, command::State}; -use snafu::{OptionExt as _, Snafu}; -use std::sync::LazyLock; -use twilight_model::{ - application::{ - command::{Command, CommandType}, - interaction::Interaction, - }, - channel::message::{Embed, MessageFlags}, - http::interaction::{InteractionResponse, InteractionResponseType}, - id::{ - Id, - marker::{ChannelMarker, GuildMarker}, - }, -}; -use twilight_util::builder::{ - InteractionResponseDataBuilder, command::CommandBuilder, embed::EmbedBuilder, -}; - -const NAME: &str = "join"; -const DESCRIPTION: &str = "The bot will join the same VC as you (with intention to record)"; - -pub static COMMAND: LazyLock = LazyLock::new(|| { - CommandBuilder::new(NAME, DESCRIPTION, CommandType::ChatInput) - .validate() - .expect("command wasn't correct") - .build() -}); - -#[derive(Debug, Snafu)] -enum GetGuildAndVoiceChannelIdError { - /// this command was not used inside a guild (Discord server) - NotInGuild, - - /// there is no user who invoked this command - NoUser, - - /// the user is not in a voice chat in this guild - UserNotInVC, -} - -#[tracing::instrument] -fn get_guild_and_voice_channel_id( - interaction: &Interaction, - vcs: &VCs, -) -> Result<(Id, Id), GetGuildAndVoiceChannelIdError> { - let guild_id = interaction.guild_id.context(NotInGuildSnafu)?; - - let user_id = interaction - .member - .as_ref() - .and_then(|member| member.user.as_ref().map(|user| user.id)) - .context(NoUserSnafu)?; - - let &voice_channel_id = vcs - .get(&guild_id) - .context(UserNotInVCSnafu)? - .get_left_for(&user_id) - .context(UserNotInVCSnafu)?; - - Ok((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 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() - }, - GetGuildAndVoiceChannelIdError::UserNotInVC => { - EmbedBuilder::new().title("You're not in a VC").description("This bot can't follow you into VC if you aren't in one in this server.").validate().unwrap().build() - }, - } -} - -#[tracing::instrument(skip(state))] -pub async fn handle(state: State, interaction: Interaction) { - let guild_and_voice_channel_id_res = - { get_guild_and_voice_channel_id(&interaction, &state.vcs_sender.borrow()) }; - let (guild_id, voice_channel_id) = match guild_and_voice_channel_id_res { - 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"); - - return; - } - }; - - state - .discord_client - .interaction(state.discord_application_id) - .create_response( - interaction.id, - &interaction.token, - &InteractionResponse { - kind: InteractionResponseType::DeferredChannelMessageWithSource, - data: None, - }, - ) - .await - .expect("TODO"); - - match join_and_record() - .audio_channels(state.audio_channels.into()) - .audio_sample_rate(state.audio_sample_rate.into()) - .guild_id(guild_id) - .recording_manager(state.recording_manager) - .songbird(&state.songbird) - .user_manager(state.user_manager) - .voice_channel_id(voice_channel_id) - .call() - .await - { - Ok(()) => { - let channel_mention = format!("<#{voice_channel_id}>"); - - let info_mention = format!( - "", - state.discord_info_command_name, state.discord_info_command_id - ); - let opt_in_mention = format!( - "", - state.discord_opt_in_command_name, state.discord_opt_in_command_id - ); - let opt_out_mention = format!( - "", - state.discord_opt_out_command_name, state.discord_opt_out_command_id - ); - - state - .discord_client - .interaction(state.discord_application_id) - .update_response( - &interaction.token, - ).embeds(Some(&[ - EmbedBuilder::new() - .title("Joined VC to record") - .description(format!("This bot joined {channel_mention} and intends to record. You can opt out with {opt_out_mention} or explicitly opt in with {opt_in_mention} (I'd appreciate this one). Please use {info_mention} for more information about this bot.")) - .validate() - .unwrap() - .build() - ])) - .await - .expect("TODO"); - } - Err(join_error) => { - tracing::error!(?join_error); - let _ = state.songbird.remove(guild_id).await; - } - } -} diff --git a/src/command/leave.rs b/src/command/leave.rs deleted file mode 100644 index 791b0e6..0000000 --- a/src/command/leave.rs +++ /dev/null @@ -1,162 +0,0 @@ -use crate::VCs; -use crate::command::State; -use snafu::{OptionExt, Snafu}; -use std::sync::LazyLock; -use twilight_model::channel::message::{Embed, MessageFlags}; -use twilight_model::http::interaction::{InteractionResponse, InteractionResponseType}; -use twilight_model::id::marker::UserMarker; -use twilight_model::{ - application::{ - command::{Command, CommandType}, - interaction::Interaction, - }, - id::{ - Id, - marker::{ChannelMarker, GuildMarker}, - }, -}; -use twilight_util::builder::InteractionResponseDataBuilder; -use twilight_util::builder::command::CommandBuilder; -use twilight_util::builder::embed::EmbedBuilder; - -const NAME: &str = "leave"; -const DESCRIPTION: &str = "The bot will leave the VC it's in (so it won't record anyone anymore)"; - -pub static COMMAND: LazyLock = LazyLock::new(|| { - CommandBuilder::new(NAME, DESCRIPTION, CommandType::ChatInput) - .validate() - .expect("command wasn't correct") - .build() -}); - -#[derive(Debug, Snafu)] -pub enum GetGuildAndVoiceChannelIdError { - /// this command was not used inside a guild (Discord server) - NotInGuild, - - /// there is no user who invoked this command - NoUser, - - /// the bot is not in a voice chat in this guild - BotNotInVC, - - /// the user is not in a voice chat with the bot in this guild - UserNotInVCWithBot, -} - -#[tracing::instrument] -pub fn get_user_and_guild_and_voice_channel_id( - bot_user_id: Id, - interaction: &Interaction, - vcs: &VCs, -) -> Result<(Id, Id), GetGuildAndVoiceChannelIdError> { - let guild_id = interaction.guild_id.context(NotInGuildSnafu)?; - - let user_id = interaction - .member - .as_ref() - .and_then(|member| member.user.as_ref().map(|user| user.id)) - .context(NoUserSnafu)?; - - let &bot_voice_channel_id = vcs - .get(&guild_id) - .context(BotNotInVCSnafu)? - .get_left_for(&bot_user_id) - .context(BotNotInVCSnafu)?; - - let &user_voice_channel_id = vcs - .get(&guild_id) - .context(UserNotInVCWithBotSnafu)? - .get_left_for(&user_id) - .context(UserNotInVCWithBotSnafu)?; - - if user_voice_channel_id != bot_voice_channel_id { - return Err(GetGuildAndVoiceChannelIdError::UserNotInVCWithBot); - } - - Ok((guild_id, bot_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::BotNotInVC => { - EmbedBuilder::new().title("Not in a VC").description("This bot can't leave VC if it isn't in one in this server.").validate().unwrap().build() - }, - GetGuildAndVoiceChannelIdError::UserNotInVCWithBot => { - EmbedBuilder::new().title("Not in a VC with the Bot").description("You have to be in the VC with the bot to make it leave (to prevent griefing and abuse).").validate().unwrap().build() - }, - } -} - -#[tracing::instrument] -pub async fn handle(state: State, interaction: Interaction) { - let guild_and_voice_channel_id_result = { - get_user_and_guild_and_voice_channel_id( - state.discord_user_id, - &interaction, - &state.vcs_sender.borrow(), - ) - }; - let (guild_id, voice_channel_id) = match guild_and_voice_channel_id_result { - 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"); - - return; - } - }; - - state.songbird.leave(guild_id).await.expect("TODO"); - - tracing::error!("TODO: successfully left the call"); - - let channel_mention = format!("<#{voice_channel_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("Left VC") - .description(format!( - "This bot left {channel_mention} (and is thereby unable to record anymore)." - )) - .validate() - .unwrap() - .build() - ]) - .flags(MessageFlags::EPHEMERAL) - .build(), - ), - },) - .await - .expect("TODO"); -} diff --git a/src/command/mod.rs b/src/command/mod.rs index e6bf761..cb56de8 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -21,8 +21,6 @@ use crate::{ }; pub mod info; -pub mod join; -pub mod leave; pub mod opt_in; pub mod opt_out; pub mod render; @@ -65,8 +63,6 @@ where pub fn all() -> Vec<(&'static Command, BoxedHandler)> { vec![ (&info::COMMAND, box_handler(info::handle)), - (&join::COMMAND, box_handler(join::handle)), - (&leave::COMMAND, box_handler(leave::handle)), (&opt_in::COMMAND, box_handler(opt_in::handle)), (&opt_out::COMMAND, box_handler(opt_out::handle)), (&render::COMMAND, box_handler(render::handle)),