feat: ability to join call with user

This commit is contained in:
2026-04-02 15:38:05 -04:00
parent 7f569629db
commit 663586a65c
3 changed files with 57 additions and 24 deletions

View File

@@ -33,6 +33,16 @@ pub async fn handle(state: State, interaction: Interaction) {
let guild_vcs = vcs.get(&guild_id).expect("TODO"); let guild_vcs = vcs.get(&guild_id).expect("TODO");
tracing::error!(?guild_vcs, "TODO"); tracing::error!(?guild_vcs, "TODO");
let user_in_vc_data = guild_vcs.get_left_and_data_for(&user_id); let (&channel_id, user_in_vc_data) = guild_vcs.get_left_and_data_for(&user_id).expect("TODO");
tracing::error!(?user_in_vc_data, "TODO"); tracing::error!(?user_in_vc_data, "TODO");
let call = tokio::spawn({
let songbird = state.songbird.clone();
async move { songbird.join(guild_id, channel_id).await }
})
.await
.unwrap()
.expect("TODO");
tracing::error!(?call, "TODO");
} }

View File

@@ -2,6 +2,7 @@ use std::{fmt::Debug, sync::Arc};
use futures::future::BoxFuture; use futures::future::BoxFuture;
use patricia_tree::StringPatriciaMap; use patricia_tree::StringPatriciaMap;
use songbird::Songbird;
use twilight_model::application::{command::Command, interaction::Interaction}; use twilight_model::application::{command::Command, interaction::Interaction};
use crate::VCs; use crate::VCs;
@@ -13,6 +14,7 @@ mod opt_out;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct State { pub struct State {
pub vcs: Arc<VCs>, pub vcs: Arc<VCs>,
pub songbird: Arc<Songbird>,
} }
type Return = (); type Return = ();

View File

@@ -1,10 +1,10 @@
use clap::Parser; use clap::Parser;
use fomo_reducer::{CommandRouter, State, VCs, all_commands, initialize_vcs, update_vcs}; use fomo_reducer::{CommandRouter, State, all_commands, initialize_vcs, update_vcs};
use opendal::{IntoOperatorUri, Operator, OperatorUri}; use opendal::{IntoOperatorUri, Operator, OperatorUri};
use secrecy::{ExposeSecret, SecretString}; use secrecy::{ExposeSecret, SecretString};
use snafu::Snafu; use snafu::Snafu;
use songbird::shards::TwilightMap; use songbird::{Songbird, shards::TwilightMap};
use std::{collections::HashMap, fmt::Debug, str::FromStr, sync::Arc}; use std::{fmt::Debug, str::FromStr, sync::Arc};
use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan}; use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan};
use twilight_gateway::{Event, EventTypeFlags, Intents, Shard, ShardId, StreamExt}; use twilight_gateway::{Event, EventTypeFlags, Intents, Shard, ShardId, StreamExt};
use twilight_model::{ use twilight_model::{
@@ -62,7 +62,11 @@ struct AppArgs {
#[derive(Parser)] #[derive(Parser)]
struct LoggingArgs { struct LoggingArgs {
#[arg(long = "logging-directives", env = "RUST_LOG", default_value = "warn,fomo_reducer=debug")] #[arg(
long = "logging-directives",
env = "RUST_LOG",
default_value = "warn,fomo_reducer=debug"
)]
env_filter: EnvFilter, env_filter: EnvFilter,
} }
@@ -106,20 +110,18 @@ async fn main() -> Result<(), MainError> {
.install_default() .install_default()
.unwrap(); .unwrap();
let shard_id = ShardId::new(0, 1);
let intents = Intents::GUILD_VOICE_STATES;
let mut shard = Shard::new(shard_id, discord_token.expose_secret().to_owned(), intents);
let senders = TwilightMap::new(FromIterator::from_iter([(
shard.id().number(),
shard.sender(),
)]));
let event_types = EventTypeFlags::GUILD_VOICE_STATES | EventTypeFlags::INTERACTION_CREATE;
let mut next_event = shard.next_event(event_types);
let discord_client = twilight_http::Client::new(discord_token.expose_secret().to_owned()); let discord_client = twilight_http::Client::new(discord_token.expose_secret().to_owned());
let user = discord_client
.current_user()
.await
.expect("couldn't fetch current user") // TODO
.model()
.await
.expect("couldn't deserialize current user"); // TODO
let user_id = user.id;
let current_application = discord_client let current_application = discord_client
.current_user_application() .current_user_application()
.await .await
@@ -132,6 +134,22 @@ async fn main() -> Result<(), MainError> {
let application_id = current_application.id; let application_id = current_application.id;
let shard_id = ShardId::new(0, 1);
let intents = Intents::GUILD_VOICE_STATES;
let mut shard = Shard::new(shard_id, discord_token.expose_secret().to_owned(), intents);
let senders = TwilightMap::new(FromIterator::from_iter([(
shard.id().number(),
shard.sender(),
)]));
let senders = Arc::new(senders);
let songbird = Songbird::twilight(senders, user_id);
let event_types = EventTypeFlags::GUILD_VOICE_STATES | EventTypeFlags::INTERACTION_CREATE;
let mut next_event = shard.next_event(event_types);
let interaction_client = discord_client.interaction(application_id); let interaction_client = discord_client.interaction(application_id);
let commands = all_commands(); let commands = all_commands();
@@ -155,12 +173,15 @@ async fn main() -> Result<(), MainError> {
let vcs = initialize_vcs(&discord_client).await; let vcs = initialize_vcs(&discord_client).await;
let vcs = Arc::new(vcs); let vcs = Arc::new(vcs);
let songbird = Arc::new(songbird);
let state = State { vcs, songbird };
while let Some(event_res) = next_event.await { while let Some(event_res) = next_event.await {
match event_res { match event_res {
Ok(event) => { Ok(event) => {
tracing::debug!(?vcs, "before handling"); handle_event(&command_router, state.clone(), event).await;
handle_event(&command_router, vcs.clone(), event).await;
tracing::debug!(?vcs, "after handling");
} }
Err(error) => { Err(error) => {
tracing::error!(?error); tracing::error!(?error);
@@ -173,11 +194,12 @@ async fn main() -> Result<(), MainError> {
Ok(()) Ok(())
} }
#[tracing::instrument(skip(command_router, vcs))] #[tracing::instrument(skip(command_router))]
async fn handle_event(command_router: &CommandRouter, vcs: Arc<VCs>, event: Event) { async fn handle_event(command_router: &CommandRouter, state: State, event: Event) {
state.songbird.process(&event).await;
match event { match event {
Event::VoiceStateUpdate(voice_state_update) => { Event::VoiceStateUpdate(voice_state_update) => {
update_vcs(&voice_state_update, &vcs); update_vcs(&voice_state_update, &state.vcs);
} }
Event::InteractionCreate(interaction_create) => { Event::InteractionCreate(interaction_create) => {
let InteractionCreate(interaction) = *interaction_create; let InteractionCreate(interaction) = *interaction_create;
@@ -188,7 +210,6 @@ async fn handle_event(command_router: &CommandRouter, vcs: Arc<VCs>, event: Even
} }
Some(InteractionData::ApplicationCommand(command_data)) => { Some(InteractionData::ApplicationCommand(command_data)) => {
let command_name = &command_data.name.clone(); let command_name = &command_data.name.clone();
let state = State { vcs };
command_router command_router
.handle(state, command_name, interaction) .handle(state, command_name, interaction)
.await; .await;