diff --git a/src/heat_seek.rs b/src/heat_seek.rs new file mode 100644 index 0000000..88cb54c --- /dev/null +++ b/src/heat_seek.rs @@ -0,0 +1,153 @@ +use std::{collections::BTreeMap, num::NonZero}; + +use tokio::sync::watch; +use twilight_model::id::{ + Id, + marker::{ChannelMarker, GuildMarker}, +}; +use twilight_util::builder::embed::EmbedBuilder; + +use crate::{OneToManyUniqueBTreeMap, State, call::join_and_record}; + +type Heat = u64; +type Hot = NonZero; +type HotOption = Option; + +type ChannelHeat = BTreeMap, Heat>; +type HeatMap = OneToManyUniqueBTreeMap>; + +#[tracing::instrument] +async fn map_heat( + mut channel_heat_watcher: watch::Receiver, + heat_map_sender: watch::Sender, +) { + loop { + heat_map_sender.send_modify(|heat_map| { + for (&channel, &heat) in &*channel_heat_watcher.borrow() { + heat_map.insert(heat, channel); + } + }); + + if let Err(_closed) = channel_heat_watcher.changed().await { + break; + } + } +} + +#[tracing::instrument] +async fn track_hottest_vc( + mut heat_map_watcher: watch::Receiver, + hottest_vc_sender: watch::Sender>>, +) { + loop { + let new_hottest_vc_option = { + heat_map_watcher + .borrow() + .last_left_and_rights() + .and_then(|(&heat, hottest_vcs)| { + let hot_option = Hot::new(heat); + + // TODO: tiebreak by whichever one this bot is already in + hot_option.map(|_| *hottest_vcs.first().unwrap()) + }) + }; + + hottest_vc_sender.send_if_modified(|old_hottest_vc_option| { + let modified = (*old_hottest_vc_option) != new_hottest_vc_option; + *old_hottest_vc_option = new_hottest_vc_option; + modified + }); + + if let Err(_closed) = heat_map_watcher.changed().await { + break; + } + } +} + +#[tracing::instrument] +async fn follow_heat( + state: State, + guild_id: Id, + mut hottest_vc_watcher: watch::Receiver>>, +) { + loop { + let hottest_vc_option = { *hottest_vc_watcher.borrow() }; + + match hottest_vc_option { + Some(hottest_vc) => { + match join_and_record() + .audio_channels(state.audio_channels) + .audio_sample_rate(state.audio_sample_rate) + .guild_id(guild_id) + .recording_data(state.recording_data.clone()) + .songbird(&state.songbird) + .user_data_manager(state.user_data_manager.clone()) + .voice_channel_id(hottest_vc) + .call() + .await + { + Ok(()) => { + let text_channel = state + .discord_voice_channel_corresponding_text_channel + .get(&guild_id) + .and_then(|guild_mappings| { + guild_mappings.get_left_for(&hottest_vc).copied() + }) + .unwrap_or(hottest_vc); + + let vc_mention = format!("<#{hottest_vc}>"); + + 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 + ); + + if let Err(posting_recording_disclosure_error) = state + .discord_client + .create_message(text_channel) + .embeds(&[ + EmbedBuilder::new() + .title("Joined VC to record") + .description(format!("This bot joined {vc_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 { + tracing::error!(?text_channel, ?posting_recording_disclosure_error, "couldn't post a recording disclosure"); + } + } + Err(joining_to_record_error) => { + tracing::error!( + ?hottest_vc, + ?joining_to_record_error, + "couldn't join to record" + ); + } + } + } + None => { + if let Err(leaving_error) = state.songbird.leave(guild_id).await { + tracing::error!(?leaving_error, "couldn't leave vc"); + } + } + } + + if let Err(_closed) = hottest_vc_watcher.changed().await { + break; + } + } +} + +#[tracing::instrument] +pub async fn heat_seek(state: State) { + todo!(); +} diff --git a/src/lib.rs b/src/lib.rs index 58f84b2..ce8e1d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ mod bot_data; mod call; pub mod command; +mod heat_seek; mod one_to_many; mod one_to_many_with_data; mod one_to_one;