diff --git a/src/lib.rs b/src/lib.rs index 03d9e42..70e202b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; diff --git a/src/main.rs b/src/main.rs index 1204a7b..e669503 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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, UserInVCData>; - -type VCs = BTreeMap, 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, } +#[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"); diff --git a/src/track_vcs.rs b/src/track_vcs.rs new file mode 100644 index 0000000..3de8bbd --- /dev/null +++ b/src/track_vcs.rs @@ -0,0 +1,102 @@ +type VCsInServer = OneToManyUniqueBTreeMapWithData, Id, UserInVCData>; + +pub type VCs = BTreeMap, 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?!"); + } + } +}