diff --git a/scripty_audio_handler/src/audio_handler.rs b/scripty_audio_handler/src/audio_handler.rs index cfaab0f..a8464bf 100644 --- a/scripty_audio_handler/src/audio_handler.rs +++ b/scripty_audio_handler/src/audio_handler.rs @@ -88,7 +88,7 @@ impl AudioHandler { kiai_client: KiaiApiClient, ephemeral: bool, ) -> Result { - let maps = SsrcMaps { + let ssrc_state = Arc::new(SsrcMaps { ssrc_user_id_map: DashMap::with_capacity_and_hasher(10, RandomState::new()), ssrc_stream_map: DashMap::with_capacity_and_hasher(10, RandomState::new()), ssrc_user_data_map: DashMap::with_capacity_and_hasher(10, RandomState::new()), @@ -97,7 +97,10 @@ impl AudioHandler { ssrc_speaking_set: DashSet::with_capacity_and_hasher(10, RandomState::new()), active_user_set: DashSet::with_capacity_and_hasher(10, RandomState::new()), next_user_list: RwLock::new(VecDeque::with_capacity(10)), - }; + }); + crate::INTERNAL_SSRC_MAPS + .get_or_init(|| DashMap::with_hasher(RandomState::new())) + .insert(guild_id, ssrc_state.clone()); let alive_call = CallDeath::new( get_data(&context).existing_calls.clone(), guild_id, @@ -106,7 +109,7 @@ impl AudioHandler { .ok_or_else(Error::already_exists)?; let this = Self { - ssrc_state: Arc::new(maps), + ssrc_state, guild_id, channel_id, voice_channel_id, @@ -179,6 +182,9 @@ impl AudioHandler { crate::VOICE_HANDLER_UPDATES .get_or_init(|| DashMap::with_hasher(RandomState::new())) .remove(&guild_id); + crate::INTERNAL_SSRC_MAPS + .get_or_init(|| DashMap::with_hasher(RandomState::new())) + .remove(&guild_id); break; } diff --git a/scripty_audio_handler/src/lib.rs b/scripty_audio_handler/src/lib.rs index 7d13536..f60164c 100644 --- a/scripty_audio_handler/src/lib.rs +++ b/scripty_audio_handler/src/lib.rs @@ -23,6 +23,8 @@ use songbird::{driver::DecodeMode, Config}; pub use songbird::{error::JoinError, Songbird}; use tokio::sync::{broadcast, oneshot::Sender}; +use crate::audio_handler::SsrcMaps; + pub fn get_songbird_config() -> Config { Config::default().decode_mode(DecodeMode::Decode) } @@ -52,6 +54,8 @@ static AUTO_LEAVE_TASKS: OnceCell, ahash::RandomStat static VOICE_HANDLER_UPDATES: OnceCell< DashMap, ahash::RandomState>, > = OnceCell::new(); +static INTERNAL_SSRC_MAPS: OnceCell, ahash::RandomState>> = + OnceCell::new(); /// Asynchronously force a handler update. If the bot has left the VC, this will run cleanup /// tasks. @@ -68,3 +72,42 @@ pub fn force_handler_update(guild_id: &GuildId) { None => debug!(%guild_id, "not actively in call for this server"), } } + +#[derive(Debug)] +pub struct InternalSsrcStateDetails { + /// All seen SSRCs + seen_users: Vec, + /// List of SSRCs who have an active stream pending + ssrcs_with_stream: Vec, + /// All SSRCs that have a user details tuple attached + ssrcs_with_attached_data: Vec, + /// List of SSRCs that are currently being ignored by the bot + ignored_ssrcs: Vec, + /// All actively speaking SSRCs + ssrcs_actively_speaking_this_tick: Vec, + /// All SSRCs currently being transcribed + actively_transcribed_ssrcs: Vec, + /// Next SSRCs to be pushed to actively_transcribed_ssrcs when it drops below the required threshold + next_ssrcs: Vec, +} + +/// Get details about internal state of Scripty +pub fn get_internal_state(guild_id: &GuildId) -> Option { + let maps = INTERNAL_SSRC_MAPS.get_or_init(|| DashMap::with_hasher(ahash::RandomState::new())); + + let internal_maps = maps.get(guild_id)?; + let v = internal_maps.value(); + + // go ahead and try getting rid of this binding i dare you :) + let ret = Some(InternalSsrcStateDetails { + seen_users: v.ssrc_user_id_map.iter().map(|x| *x.key()).collect(), + ssrcs_with_stream: v.ssrc_stream_map.iter().map(|x| *x.key()).collect(), + ssrcs_with_attached_data: v.ssrc_user_data_map.iter().map(|x| *x.key()).collect(), + ignored_ssrcs: v.ssrc_ignored_map.iter().map(|x| *x.key()).collect(), + ssrcs_actively_speaking_this_tick: v.ssrc_speaking_set.iter().map(|x| *x).collect(), + actively_transcribed_ssrcs: v.active_user_set.iter().map(|x| *x).collect(), + next_ssrcs: v.next_user_list.read().iter().map(|x| *x).collect(), + }); + + ret +} diff --git a/scripty_commands/src/cmds/debug.rs b/scripty_commands/src/cmds/debug.rs new file mode 100644 index 0000000..6cd0039 --- /dev/null +++ b/scripty_commands/src/cmds/debug.rs @@ -0,0 +1,31 @@ +use poise::CreateReply; +use serenity::builder::CreateAttachment; + +use crate::{Context, Error}; + +/// Output some data useful for debugging Scripty +#[poise::command(prefix_command, slash_command, guild_only)] +pub async fn debug(ctx: Context<'_>) -> Result<(), Error> { + let guild_id = ctx.guild_id().ok_or_else(Error::expected_guild)?; + let resolved_language = + scripty_i18n::get_resolved_language(ctx.author().id.get(), Some(guild_id.get())).await; + let state = scripty_audio_handler::get_internal_state(&guild_id); + if let Some(state) = state { + ctx.send( + CreateReply::new() + .content(format_message!(resolved_language, "debug-info-message")) + .attachment(CreateAttachment::bytes( + format!("{:?}", state), + Cow::Borrowed("debug_info.txt"), + )), + ) + .await?; + } else { + ctx.send( + CreateReply::new().content(format_message!(resolved_language, "debug-not-in-call")), + ) + .await?; + } + + Ok(()) +} diff --git a/scripty_commands/src/cmds/mod.rs b/scripty_commands/src/cmds/mod.rs index c34cd7a..c8193f9 100644 --- a/scripty_commands/src/cmds/mod.rs +++ b/scripty_commands/src/cmds/mod.rs @@ -2,6 +2,7 @@ mod admin; pub mod automod; pub mod config; mod data_storage; +mod debug; pub mod dm_support; mod entity_block; mod help; @@ -17,6 +18,7 @@ mod vote_reminders; pub use admin::*; pub use data_storage::*; +pub use debug::debug; pub use dm_support::*; pub use entity_block::*; pub use help::help; diff --git a/scripty_commands/src/lib.rs b/scripty_commands/src/lib.rs index a8f2d4b..ed3fbd2 100644 --- a/scripty_commands/src/lib.rs +++ b/scripty_commands/src/lib.rs @@ -20,6 +20,7 @@ pub fn build_commands() -> Vec> { cmds::user_language(), cmds::vote_reminder(), cmds::transcribe_message(), + cmds::debug(), poise::Command { subcommands: vec![cmds::block_user(), cmds::block_guild()], ..cmds::block() diff --git a/scripty_i18n/locales/en.ftl b/scripty_i18n/locales/en.ftl index aa4e8e8..e7deef4 100644 --- a/scripty_i18n/locales/en.ftl +++ b/scripty_i18n/locales/en.ftl @@ -175,6 +175,12 @@ config-kiai-info = You can find more info about Kiai at [kiai.app](https://www.k If you use this integration, be sure to disable Kiai's voice XP module as they will conflict. config-kiai-missing-perms = Scripty is missing permissions to work in this server. Authorize it with the `/application authorize` command, using an application ID of `811652199100317726`, and giving Scripty the "view and edit all levels and XP" permission. +## debug command +cmds_debug = debug + .description = Output debugging information about Scripty internal state. +debug-info-message = Forward this message to whoever in the Scripty support server is asking you for it. +debug-not-in-call = This command is useless if Scripty isn't in a VC. + ## Help menu translation strings command-not-found = No command with name `{ $commandName }` found.