Compare commits

..

2 Commits

Author SHA1 Message Date
5cb938aa24 chore: format 2026-05-24 13:28:02 -04:00
56ec8aaf8c feat: allow anyone in a VC being recorded to make the bot leave 2026-05-24 13:27:53 -04:00
4 changed files with 41 additions and 54 deletions

View File

@@ -16,12 +16,10 @@ use std::{
time::Instant, time::Instant,
}; };
use time::UtcDateTime; use time::UtcDateTime;
use twilight_model:: use twilight_model::id::{
id::{
Id, Id,
marker::{ChannelMarker, GuildMarker, UserMarker}, marker::{ChannelMarker, GuildMarker, UserMarker},
} };
;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct Handler { struct Handler {

View File

@@ -1,10 +1,6 @@
use crate::{ use crate::{VCs, call::join_and_record, command::State};
VCs, call::join_and_record, command::State,
};
use snafu::{OptionExt as _, Snafu}; use snafu::{OptionExt as _, Snafu};
use std:: use std::sync::LazyLock;
sync::LazyLock
;
use twilight_model::{ use twilight_model::{
application::{ application::{
command::{Command, CommandType}, command::{Command, CommandType},
@@ -131,7 +127,8 @@ pub async fn handle(state: State, interaction: Interaction) {
.songbird(&state.songbird) .songbird(&state.songbird)
.user_data_manager(state.user_data_manager) .user_data_manager(state.user_data_manager)
.voice_channel_id(voice_channel_id) .voice_channel_id(voice_channel_id)
.call().await .call()
.await
{ {
Ok(()) => { Ok(()) => {
let channel_mention = format!("<#{voice_channel_id}>"); let channel_mention = format!("<#{voice_channel_id}>");

View File

@@ -39,6 +39,9 @@ pub enum GetGuildAndVoiceChannelIdError {
/// the bot is not in a voice chat in this guild /// the bot is not in a voice chat in this guild
BotNotInVC, BotNotInVC,
/// the user is not in a voice chat with the bot in this guild
UserNotInVCWithBot,
} }
#[tracing::instrument] #[tracing::instrument]
@@ -46,22 +49,32 @@ 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<UserMarker>, Id<GuildMarker>, Id<ChannelMarker>), GetGuildAndVoiceChannelIdError> { ) -> Result<(Id<GuildMarker>, Id<ChannelMarker>), GetGuildAndVoiceChannelIdError> {
let guild_id = interaction.guild_id.context(NotInGuildSnafu)?;
let user_id = interaction let user_id = interaction
.member .member
.as_ref() .as_ref()
.and_then(|member| member.user.as_ref().map(|user| user.id)) .and_then(|member| member.user.as_ref().map(|user| user.id))
.context(NoUserSnafu)?; .context(NoUserSnafu)?;
let guild_id = interaction.guild_id.context(NotInGuildSnafu)?; let &bot_voice_channel_id = vcs
let &voice_channel_id = vcs
.get(&guild_id) .get(&guild_id)
.context(BotNotInVCSnafu)? .context(BotNotInVCSnafu)?
.get_left_for(&bot_user_id) .get_left_for(&bot_user_id)
.context(BotNotInVCSnafu)?; .context(BotNotInVCSnafu)?;
Ok((user_id, guild_id, voice_channel_id)) 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 { fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Embed {
@@ -75,20 +88,23 @@ fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Emb
GetGuildAndVoiceChannelIdError::BotNotInVC => { 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() 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] #[tracing::instrument]
pub async fn handle(state: State, interaction: Interaction) { pub async fn handle(state: State, interaction: Interaction) {
let user_and_guild_and_voice_channel_id_res = { let guild_and_voice_channel_id_result = {
get_user_and_guild_and_voice_channel_id( get_user_and_guild_and_voice_channel_id(
state.discord_user_id, state.discord_user_id,
&interaction, &interaction,
&state.vcs_sender.borrow(), &state.vcs_sender.borrow(),
) )
}; };
let (user_id, guild_id, voice_channel_id) = match user_and_guild_and_voice_channel_id_res { let (guild_id, voice_channel_id) = match guild_and_voice_channel_id_result {
Ok((user_id, guild_id, voice_channel_id)) => (user_id, guild_id, voice_channel_id), Ok((guild_id, voice_channel_id)) => (guild_id, voice_channel_id),
Err(error) => { Err(error) => {
state state
.discord_client .discord_client
@@ -113,31 +129,6 @@ 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 this 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

@@ -1,6 +1,7 @@
use clap::Parser; use clap::Parser;
use fomo_reducer::{ use fomo_reducer::{
BotDataManager, CommandRouter, GuildVoiceChannelToTextChannel, State, Storage, UserDataManager, VCsSender, all_commands, command, heat_seek, initialize_vcs, update_vcs BotDataManager, CommandRouter, GuildVoiceChannelToTextChannel, State, Storage, UserDataManager,
VCsSender, all_commands, command, heat_seek, initialize_vcs, update_vcs,
}; };
use secrecy::{ExposeSecret, SecretString}; use secrecy::{ExposeSecret, SecretString};
use snafu::{OptionExt, ResultExt, Snafu}; use snafu::{OptionExt, ResultExt, Snafu};