chore: extract out Discord event handling

This commit is contained in:
2026-03-20 15:08:28 -04:00
parent e759b78d20
commit 56f63764a5
3 changed files with 119 additions and 105 deletions

View File

@@ -1,10 +1,12 @@
mod one_to_many;
mod one_to_many_with_data;
mod one_to_one;
mod track_vcs;
mod vc_user;
pub use one_to_many::OneToManyUniqueBTreeMap;
pub use one_to_many_with_data::OneToManyUniqueBTreeMapWithData;
pub use one_to_one::OneToOneBTreeMap;
pub use track_vcs::{VCs, initialize_vcs, update_vcs};
pub use vc_user::{UserInVCData, VoiceStatus};

View File

@@ -1,29 +1,14 @@
use std::{
collections::BTreeMap,
fmt::{Debug, Display},
str::FromStr,
};
use clap::Parser;
use fomo_reducer::{VCs, initialize_vcs, update_vcs};
use opendal::{IntoOperatorUri, Operator, OperatorUri};
use secrecy::{ExposeSecret, SecretString};
use snafu::Snafu;
use std::{fmt::Debug, str::FromStr};
use tracing_subscriber::fmt::format::FmtSpan;
use twilight_gateway::{
Event, EventTypeFlags, Intents, Shard, ShardId, StreamExt, error::ReceiveMessageErrorType,
};
use twilight_model::{
channel::ChannelType,
id::{
Id,
marker::{ChannelMarker, GuildMarker, UserMarker},
},
};
use typed_builder::TypedBuilder;
use vc_notifier::{OneToManyUniqueBTreeMapWithData, UserInVCData, VoiceStatus};
type VCsInServer = OneToManyUniqueBTreeMapWithData<Id<ChannelMarker>, Id<UserMarker>, UserInVCData>;
type VCs = BTreeMap<Id<GuildMarker>, VCsInServer>;
use twilight_model::id::{Id, marker::UserMarker};
#[derive(Clone)]
struct OpendalOperator {
@@ -67,10 +52,17 @@ struct Args {
#[arg(long, env)]
storage: OpendalOperator,
#[arg(long, env)]
bot_owner: Id<UserMarker>,
}
#[derive(Debug, Snafu)]
enum MainError {}
#[snafu::report]
#[tokio::main]
async fn main() {
async fn main() -> Result<(), MainError> {
tracing_subscriber::fmt()
.pretty()
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
@@ -83,6 +75,7 @@ async fn main() {
let Args {
discord_token,
storage,
bot_owner,
} = args;
rustls::crypto::aws_lc_rs::default_provider()
@@ -112,98 +105,15 @@ async fn main() {
next_event = shard.next_event(vc_event_types);
}
}
#[tracing::instrument(skip(discord_client), ret)]
async fn initialize_vcs(discord_client: &twilight_http::Client) -> VCs {
let mut vcs = VCs::default();
if let Ok(guilds_res) = discord_client.current_user_guilds().limit(200).await
&& let Ok(guilds) = guilds_res.model().await
{
for guild in guilds {
if let Ok(guild_members_res) = discord_client.guild_members(guild.id).limit(999).await
&& let Ok(guild_members) = guild_members_res.model().await
{
for member in guild_members {
if let Ok(voice_state_res) = discord_client
.user_voice_state(guild.id, member.user.id)
.await
&& let Ok(voice_state) = voice_state_res.model().await
{
tracing::info!(?member.user.id, ?voice_state);
let voice_status = VoiceStatus::builder()
.self_deafened(voice_state.self_deaf)
.self_muted(voice_state.self_mute)
.server_deafened(voice_state.deaf)
.server_muted(voice_state.mute)
.camming(voice_state.self_video)
.streaming(voice_state.self_stream)
.build();
let user_in_vc_data = voice_status.into();
if let Some(channel_id) = voice_state.channel_id {
vcs.entry(guild.id).or_default().insert(
channel_id,
member.user.id,
user_in_vc_data,
);
}
}
}
}
}
}
vcs
Ok(())
}
#[tracing::instrument(skip(vcs))]
async fn handle_event(event: Event, vcs: &mut VCs) {
match event {
Event::VoiceStateUpdate(voice_state_update) => {
let user_id = voice_state_update.user_id;
match voice_state_update.guild_id {
Some(guild_id) => match voice_state_update.channel_id {
Some(channel_id) => {
let voice_status = VoiceStatus::builder()
.self_deafened(voice_state_update.self_deaf)
.self_muted(voice_state_update.self_mute)
.server_deafened(voice_state_update.deaf)
.server_muted(voice_state_update.mute)
.camming(voice_state_update.self_video)
.streaming(voice_state_update.self_stream)
.build();
let user_in_vc_data = voice_status.into();
vcs.entry(guild_id).or_default().insert(
channel_id,
user_id,
user_in_vc_data,
);
tracing::info!(
?guild_id,
?channel_id,
?user_id,
"connected or otherwise changed state while connected"
);
}
None => {
if let Some(channel_vcers) = vcs.get_mut(&guild_id) {
channel_vcers.remove_right(&user_id);
}
tracing::info!(?guild_id, ?user_id, "disconnected");
}
},
None => {
tracing::error!("why doesn't this have a guild id attached?!");
}
}
update_vcs(&voice_state_update, vcs);
}
other => {
tracing::warn!(?other, "wasn't expected");

102
src/track_vcs.rs Normal file
View File

@@ -0,0 +1,102 @@
type VCsInServer = OneToManyUniqueBTreeMapWithData<Id<ChannelMarker>, Id<UserMarker>, UserInVCData>;
pub type VCs = BTreeMap<Id<GuildMarker>, VCsInServer>;
use std::collections::BTreeMap;
use twilight_model::{
gateway::payload::incoming::VoiceStateUpdate,
id::{
Id,
marker::{ChannelMarker, GuildMarker, UserMarker},
},
};
use crate::{OneToManyUniqueBTreeMapWithData, UserInVCData, VoiceStatus};
#[tracing::instrument(skip(discord_client), ret)]
pub async fn initialize_vcs(discord_client: &twilight_http::Client) -> VCs {
let mut vcs = VCs::default();
if let Ok(guilds_res) = discord_client.current_user_guilds().limit(200).await
&& let Ok(guilds) = guilds_res.model().await
{
for guild in guilds {
if let Ok(guild_members_res) = discord_client.guild_members(guild.id).limit(999).await
&& let Ok(guild_members) = guild_members_res.model().await
{
for member in guild_members {
if let Ok(voice_state_res) = discord_client
.user_voice_state(guild.id, member.user.id)
.await
&& let Ok(voice_state) = voice_state_res.model().await
{
tracing::info!(?member.user.id, ?voice_state);
let voice_status = VoiceStatus::builder()
.self_deafened(voice_state.self_deaf)
.self_muted(voice_state.self_mute)
.server_deafened(voice_state.deaf)
.server_muted(voice_state.mute)
.camming(voice_state.self_video)
.streaming(voice_state.self_stream)
.build();
let user_in_vc_data = voice_status.into();
if let Some(channel_id) = voice_state.channel_id {
vcs.entry(guild.id).or_default().insert(
channel_id,
member.user.id,
user_in_vc_data,
);
}
}
}
}
}
}
vcs
}
pub fn update_vcs(voice_state_update: &VoiceStateUpdate, vcs: &mut VCs) {
let user_id = voice_state_update.user_id;
match voice_state_update.guild_id {
Some(guild_id) => match voice_state_update.channel_id {
Some(channel_id) => {
let voice_status = VoiceStatus::builder()
.self_deafened(voice_state_update.self_deaf)
.self_muted(voice_state_update.self_mute)
.server_deafened(voice_state_update.deaf)
.server_muted(voice_state_update.mute)
.camming(voice_state_update.self_video)
.streaming(voice_state_update.self_stream)
.build();
let user_in_vc_data = voice_status.into();
vcs.entry(guild_id)
.or_default()
.insert(channel_id, user_id, user_in_vc_data);
tracing::info!(
?guild_id,
?channel_id,
?user_id,
"connected or otherwise changed state while connected"
);
}
None => {
if let Some(channel_vcers) = vcs.get_mut(&guild_id) {
channel_vcers.remove_right(&user_id);
}
tracing::info!(?guild_id, ?user_id, "disconnected");
}
},
None => {
tracing::error!("why doesn't this have a guild id attached?!");
}
}
}