From bca88c7a7041d7b2c6c159da67bbe36e5f83a8df Mon Sep 17 00:00:00 2001 From: "Wyatt J. Miller" Date: Sun, 2 Aug 2026 13:45:17 -0400 Subject: [PATCH 1/2] mod: mute audience feature --- src/commands/buzzer.rs | 38 +++++++++---- src/commands/controls.rs | 39 ++++++++++--- src/commands/mod.rs | 1 + src/commands/stop.rs | 11 +++- src/commands/voice.rs | 119 +++++++++++++++++++++++++++++++++++++++ src/lib/buzzer.rs | 5 ++ 6 files changed, 193 insertions(+), 20 deletions(-) create mode 100644 src/commands/voice.rs diff --git a/src/commands/buzzer.rs b/src/commands/buzzer.rs index 4b365af..f390659 100644 --- a/src/commands/buzzer.rs +++ b/src/commands/buzzer.rs @@ -4,6 +4,7 @@ use serenity::all::{ }; use super::controls::{self, ControlAction}; +use super::voice; use crate::app::buzzer::{ BUZZER_CUSTOM_ID, BuzzerSessionKey, LOCK_CUSTOM_ID, RESET_CUSTOM_ID, UNLOCK_CUSTOM_ID, buzzer_sound, session_components, @@ -87,18 +88,35 @@ async fn press(ctx: &Context, interaction: &ComponentInteraction) { } let guild_id = active_session.guild_id; - drop(session); + match songbird::get(ctx).await { + Some(manager) => match manager.get(guild_id) { + Some(call) => { + call.lock().await.play_only_input(buzzer_sound()); + } + None => { + tracing::error!(%guild_id, "bot is not connected to voice for active buzzer"); + } + }, + None => tracing::error!("voice manager unavailable while playing buzzer"), + } - let Some(manager) = songbird::get(ctx).await else { - tracing::error!("voice manager unavailable while playing buzzer"); - return; - }; - let Some(call) = manager.get(guild_id) else { - tracing::error!(%guild_id, "bot is not connected to voice for active buzzer"); - return; - }; + let exempt_user_ids = [active_session.host_id, interaction.user.id]; + let voice_result = voice::mute_channel_except(ctx, active_session, &exempt_user_ids).await; + if voice_result.failures > 0 { + tracing::warn!( + failures = voice_result.failures, + %guild_id, + "buzzer locked with participant mute failures" + ); - call.lock().await.play_only_input(buzzer_sound()); + let warning = EditInteractionResponse::new().content(format!( + "<@{}> buzzed first! I couldn't mute {} participant(s); check my Mute Members permission and role position.", + interaction.user.id, voice_result.failures, + )); + if let Err(error) = interaction.edit_response(&ctx.http, warning).await { + tracing::error!(?error, "failed to report participant mute failures"); + } + } } async fn run_control(ctx: &Context, interaction: &ComponentInteraction, action: ControlAction) { diff --git a/src/commands/controls.rs b/src/commands/controls.rs index 6c1830c..3d32e8c 100644 --- a/src/commands/controls.rs +++ b/src/commands/controls.rs @@ -1,5 +1,6 @@ use serenity::all::{Context, EditMessage, GuildId, MessageId, UserId}; +use super::voice; use crate::app::buzzer::{BuzzerSessionKey, session_components}; #[derive(Clone, Copy, Debug)] @@ -48,10 +49,6 @@ pub async fn apply( "The buzzer has been reset.", ), ControlAction::Lock => { - if !active_session.is_open { - return "The buzzer is already locked.".to_string(); - } - let content = match active_session.buzzed_user { Some(user_id) => { format!("<@{user_id}> buzzed first! The buzzer is manually locked.") @@ -61,10 +58,6 @@ pub async fn apply( (content, false, false, "The buzzer has been locked.") } ControlAction::Unlock => { - if active_session.is_open { - return "The buzzer is already unlocked.".to_string(); - } - let content = match active_session.buzzed_user { Some(user_id) => { format!("<@{user_id}> buzzed first! The buzzer is manually unlocked.") @@ -93,7 +86,28 @@ pub async fn apply( active_session.buzzed_user = None; } - success_message.to_string() + let voice_result = match action { + ControlAction::Lock => { + let mut exempt_user_ids = vec![active_session.host_id]; + if let Some(buzzed_user_id) = active_session.buzzed_user { + exempt_user_ids.push(buzzed_user_id); + } + voice::mute_channel_except(ctx, active_session, &exempt_user_ids).await + } + ControlAction::Reset | ControlAction::Unlock => { + voice::unmute_session_members(ctx, active_session).await + } + }; + + if voice_result.failures == 0 { + success_message.to_string() + } else { + format!( + "{success_message} I couldn't {} {} participant(s); check my Mute Members permission and role position.", + action.failed_voice_verb(), + voice_result.failures, + ) + } } impl ControlAction { @@ -104,4 +118,11 @@ impl ControlAction { Self::Unlock => "unlock", } } + + fn failed_voice_verb(self) -> &'static str { + match self { + Self::Lock => "mute", + Self::Reset | Self::Unlock => "unmute", + } + } } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 52206ff..2951c78 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -5,3 +5,4 @@ pub mod reset; pub mod start; pub mod stop; pub mod unlock; +mod voice; diff --git a/src/commands/stop.rs b/src/commands/stop.rs index 97609d7..5199080 100644 --- a/src/commands/stop.rs +++ b/src/commands/stop.rs @@ -1,5 +1,6 @@ use serenity::all::{CommandInteraction, Context, CreateCommand, EditMessage}; +use super::voice; use crate::app::buzzer::{BuzzerSessionKey, stopped_session_components}; pub async fn run(ctx: &Context, command: &CommandInteraction) -> String { @@ -17,7 +18,7 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) -> String { }; let mut session = session.lock().await; - let Some(active_session) = session.as_ref() else { + let Some(active_session) = session.as_mut() else { return "There is no active buzzer session.".to_string(); }; @@ -31,6 +32,14 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) -> String { let text_channel_id = active_session.text_channel_id; let button_message_id = active_session.button_message_id; + let voice_result = voice::unmute_session_members(ctx, active_session).await; + if voice_result.failures > 0 { + return format!( + "I couldn't unmute {} participant(s), so the session was not stopped. Check my Mute Members permission and role position, then try again.", + voice_result.failures, + ); + } + let Some(manager) = songbird::get(ctx).await else { return "The voice manager is unavailable.".to_string(); }; diff --git a/src/commands/voice.rs b/src/commands/voice.rs new file mode 100644 index 0000000..ac100a3 --- /dev/null +++ b/src/commands/voice.rs @@ -0,0 +1,119 @@ +use serenity::all::{Context, EditMember, UserId}; + +use crate::app::buzzer::BuzzerSession; + +#[derive(Debug, Default)] +pub struct VoiceUpdateResult { + pub failures: usize, +} + +/// Server-mutes every human participant in the session voice channel except +/// the supplied users. Pre-existing server mutes are left untouched so the bot +/// does not later undo a moderator's mute. +pub async fn mute_channel_except( + ctx: &Context, + session: &mut BuzzerSession, + exempt_user_ids: &[UserId], +) -> VoiceUpdateResult { + let bot_user_id = ctx.cache.current_user().id; + let participants = ctx.cache.guild(session.guild_id).map(|guild| { + guild + .voice_states + .values() + .filter(|state| state.channel_id == Some(session.voice_channel_id)) + .map(|state| { + let is_bot = state.user_id == bot_user_id + || state.member.as_ref().is_some_and(|member| member.user.bot); + (state.user_id, state.mute, is_bot) + }) + .collect::>() + }); + + let Some(participants) = participants else { + tracing::error!( + guild_id = %session.guild_id, + "guild unavailable while updating buzzer voice mutes" + ); + return VoiceUpdateResult { failures: 1 }; + }; + + let mut result = VoiceUpdateResult::default(); + for (user_id, already_muted, is_bot) in participants { + if is_bot { + continue; + } + + if exempt_user_ids.contains(&user_id) { + if session.muted_user_ids.contains(&user_id) + && set_server_mute(ctx, session, user_id, false).await + { + session.muted_user_ids.remove(&user_id); + } else if session.muted_user_ids.contains(&user_id) { + result.failures += 1; + } + continue; + } + + // A mute not recorded in the session belongs to a moderator or some + // other system and must not be claimed or later reversed by this bot. + if already_muted || session.muted_user_ids.contains(&user_id) { + continue; + } + + if set_server_mute(ctx, session, user_id, true).await { + session.muted_user_ids.insert(user_id); + } else { + result.failures += 1; + } + } + + result +} + +/// Unmutes only members that this buzzer session successfully muted. +pub async fn unmute_session_members( + ctx: &Context, + session: &mut BuzzerSession, +) -> VoiceUpdateResult { + let muted_user_ids = session.muted_user_ids.iter().copied().collect::>(); + let mut result = VoiceUpdateResult::default(); + + for user_id in muted_user_ids { + if set_server_mute(ctx, session, user_id, false).await { + session.muted_user_ids.remove(&user_id); + } else { + result.failures += 1; + } + } + + result +} + +async fn set_server_mute( + ctx: &Context, + session: &BuzzerSession, + user_id: UserId, + muted: bool, +) -> bool { + let builder = EditMember::new() + .mute(muted) + .audit_log_reason("Hildebrand buzzer session"); + + match session + .guild_id + .edit_member(&ctx.http, user_id, builder) + .await + { + Ok(_) => true, + Err(error) => { + tracing::error!( + ?error, + guild_id = %session.guild_id, + %user_id, + muted, + "failed to update participant server mute" + ); + false + } + } +} diff --git a/src/lib/buzzer.rs b/src/lib/buzzer.rs index 849c346..b03f3f6 100644 --- a/src/lib/buzzer.rs +++ b/src/lib/buzzer.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use std::f32::consts::TAU; use std::io::Cursor; use std::sync::Arc; @@ -24,6 +25,9 @@ pub struct BuzzerSession { pub buzzed_user: Option, pub is_open: bool, + /// Members server-muted by this session. Only these members are unmuted + /// when the buzzer is reset, unlocked, or stopped. + pub muted_user_ids: HashSet, } impl BuzzerSession { @@ -42,6 +46,7 @@ impl BuzzerSession { button_message_id, buzzed_user: None, is_open: true, + muted_user_ids: HashSet::new(), } } } -- 2.54.0 From 302fe56ff96468fdf4b82992356aadcafacbed83 Mon Sep 17 00:00:00 2001 From: "Wyatt J. Miller" Date: Sun, 2 Aug 2026 20:10:37 -0400 Subject: [PATCH 2/2] add: add mp3, removed guild register slash commands --- assets/buzz.mp3 | Bin 0 -> 22195 bytes src/lib/buzzer.rs | 32 ++++---------------------------- src/main.rs | 11 +++++++++++ 3 files changed, 15 insertions(+), 28 deletions(-) create mode 100644 assets/buzz.mp3 diff --git a/assets/buzz.mp3 b/assets/buzz.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..1c0543ed2c84b91a735889497dcf4a02787c908e GIT binary patch literal 22195 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 Vec Input { - const SAMPLE_RATE: u32 = 48_000; - const DURATION_SECONDS: f32 = 0.4; - const FREQUENCY_HZ: f32 = 180.0; + const BUZZ_MP3: &[u8] = include_bytes!("../../assets/buzz.mp3"); - let sample_count = (SAMPLE_RATE as f32 * DURATION_SECONDS) as usize; - let mut pcm = Vec::with_capacity(sample_count * 2 * size_of::()); - - for sample_index in 0..sample_count { - let time = sample_index as f32 / SAMPLE_RATE as f32; - let progress = sample_index as f32 / sample_count as f32; - let attack = (time / 0.01).min(1.0); - let release = ((1.0 - progress) / 0.08).min(1.0); - let envelope = attack * release; - - // Odd harmonics give the tone the rasp of a physical game-show buzzer. - let phase = TAU * FREQUENCY_HZ * time; - let value = - envelope * (phase.sin() + (3.0 * phase).sin() / 3.0 + (5.0 * phase).sin() / 5.0) * 0.32; - - for _ in 0..2 { - pcm.extend_from_slice(&value.to_le_bytes()); - } - } - - RawAdapter::new(Cursor::new(pcm), SAMPLE_RATE, 2).into() + BUZZ_MP3.into() } /// The application's single active buzzer session. diff --git a/src/main.rs b/src/main.rs index 2696f8b..f03dec2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,6 +83,17 @@ impl EventHandler for Handler { async fn ready(&self, ctx: Context, ready: Ready) { println!("{} is connected!", ready.user.name); + // Commands were previously registered per guild. Clear those legacy + // registrations so only the global command set appears. + for guild in &ready.guilds { + if let Err(why) = guild.id.set_commands(&ctx.http, Vec::new()).await { + println!( + "Cannot remove legacy slash commands from guild {}: {why}", + guild.id + ); + } + } + let commands = vec![ commands::start::register(), commands::reset::register(), -- 2.54.0