feat: laying the groundwork for heat seeking

This commit is contained in:
2026-05-21 00:59:05 -04:00
parent 48a0c8250b
commit 97763877d8
2 changed files with 154 additions and 0 deletions

153
src/heat_seek.rs Normal file
View 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!();
}

View File

@@ -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;