feat: laying the groundwork for heat seeking
This commit is contained in:
153
src/heat_seek.rs
Normal file
153
src/heat_seek.rs
Normal file
@@ -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<Heat>;
|
||||||
|
type HotOption = Option<Hot>;
|
||||||
|
|
||||||
|
type ChannelHeat = BTreeMap<Id<ChannelMarker>, Heat>;
|
||||||
|
type HeatMap = OneToManyUniqueBTreeMap<Heat, Id<ChannelMarker>>;
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
async fn map_heat(
|
||||||
|
mut channel_heat_watcher: watch::Receiver<ChannelHeat>,
|
||||||
|
heat_map_sender: watch::Sender<HeatMap>,
|
||||||
|
) {
|
||||||
|
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<HeatMap>,
|
||||||
|
hottest_vc_sender: watch::Sender<Option<Id<ChannelMarker>>>,
|
||||||
|
) {
|
||||||
|
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<GuildMarker>,
|
||||||
|
mut hottest_vc_watcher: watch::Receiver<Option<Id<ChannelMarker>>>,
|
||||||
|
) {
|
||||||
|
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!();
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
mod bot_data;
|
mod bot_data;
|
||||||
mod call;
|
mod call;
|
||||||
pub mod command;
|
pub mod command;
|
||||||
|
mod heat_seek;
|
||||||
mod one_to_many;
|
mod one_to_many;
|
||||||
mod one_to_many_with_data;
|
mod one_to_many_with_data;
|
||||||
mod one_to_one;
|
mod one_to_one;
|
||||||
|
|||||||
Reference in New Issue
Block a user