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 call;
|
||||
pub mod command;
|
||||
mod heat_seek;
|
||||
mod one_to_many;
|
||||
mod one_to_many_with_data;
|
||||
mod one_to_one;
|
||||
|
||||
Reference in New Issue
Block a user