feat: allow setting a corresponding text channel for a voice channel

This commit is contained in:
2026-04-17 01:19:09 -04:00
parent 612a696829
commit 62399c2046
4 changed files with 83 additions and 11 deletions

View File

@@ -1,13 +1,16 @@
use clap::Parser;
use fomo_reducer::{CommandRouter, State, Storage, all_commands, initialize_vcs, update_vcs};
use fomo_reducer::{
CommandRouter, GuildVoiceChannelToTextChannel, State, Storage, all_commands, initialize_vcs,
update_vcs,
};
use secrecy::{ExposeSecret, SecretString};
use snafu::Snafu;
use snafu::{OptionExt, ResultExt, Snafu};
use songbird::{
Config, Songbird,
driver::{Channels, DecodeConfig, SampleRate},
shards::TwilightMap,
};
use std::{fmt::Debug, sync::Arc};
use std::{fmt::Debug, str::FromStr, sync::Arc};
use strum::EnumString;
use tokio::{select, signal::ctrl_c, task::JoinSet};
use tokio_util::{sync::CancellationToken, time::FutureExt as _};
@@ -19,7 +22,10 @@ use twilight_model::{
payload::{incoming::InteractionCreate, outgoing::UpdatePresence},
presence::{ActivityType, MinimalActivity, Status},
},
id::{Id, marker::UserMarker},
id::{
Id,
marker::{ChannelMarker, GuildMarker, UserMarker},
},
};
#[derive(Clone, Copy, Debug, strum::Display, EnumString)]
@@ -63,6 +69,41 @@ impl From<AudioSampleRate> for SampleRate {
}
}
#[derive(Debug, Snafu)]
enum ParseGuildVCToTextChannelError {
NoScope,
NoRelation,
ParseGuildError {
source: <Id<GuildMarker> as FromStr>::Err,
},
ParseVoiceChannelError {
source: <Id<ChannelMarker> as FromStr>::Err,
},
ParseTextChannelError {
source: <Id<ChannelMarker> as FromStr>::Err,
},
}
fn parse_guild_vc_to_text_channel(
source: &str,
) -> Result<(Id<GuildMarker>, Id<ChannelMarker>, Id<ChannelMarker>), ParseGuildVCToTextChannelError>
{
let (guild, voice_channel_and_text_channel) = source.split_once(':').context(NoScopeSnafu)?;
let (voice_channel, text_channel) = voice_channel_and_text_channel
.split_once("->")
.context(NoRelationSnafu)?;
let guild = guild.parse().context(ParseGuildSnafu)?;
let voice_channel = voice_channel.parse().context(ParseVoiceChannelSnafu)?;
let text_channel = text_channel.parse().context(ParseTextChannelSnafu)?;
Ok((guild, voice_channel, text_channel))
}
#[derive(Debug, Parser)]
struct AppArgs {
#[arg(long, env)]
@@ -77,6 +118,10 @@ struct AppArgs {
#[arg(long, env)]
discord_status: Option<Arc<str>>,
#[arg(long, env, value_parser = parse_guild_vc_to_text_channel)]
discord_voice_channel_corresponding_text_channel:
Vec<(Id<GuildMarker>, Id<ChannelMarker>, Id<ChannelMarker>)>,
#[arg(long, env, default_value_t = AudioChannels::Mono)]
audio_channels: AudioChannels,
@@ -141,6 +186,7 @@ async fn main() -> Result<(), MainError> {
discord_bot_owner_user_id,
discord_nickname,
discord_status,
discord_voice_channel_corresponding_text_channel,
audio_channels,
audio_sample_rate,
bot_data,
@@ -244,11 +290,11 @@ async fn main() -> Result<(), MainError> {
.await
.expect("failed to deserialize set commands"); // TODO
let vcs = initialize_vcs(&discord_client).await;
let command_router = CommandRouter::from_iter(commands);
let command_router = Arc::new(command_router);
let vcs = initialize_vcs(&discord_client).await;
let discord_client = Arc::new(discord_client);
let songbird = Arc::new(songbird);
let vcs = Arc::new(vcs);
@@ -257,6 +303,22 @@ async fn main() -> Result<(), MainError> {
let recording_data = recording_data.into_inner();
let user_data = user_data.into_inner();
let discord_voice_channel_corresponding_text_channel = {
let mut map = GuildVoiceChannelToTextChannel::default();
for (guild_id, voice_channel_id, text_channel_id) in
discord_voice_channel_corresponding_text_channel
{
map.entry(guild_id)
.or_default()
.insert(voice_channel_id, text_channel_id);
}
map
};
let discord_voice_channel_corresponding_text_channel =
Arc::new(discord_voice_channel_corresponding_text_channel);
let state = State {
audio_channels,
audio_sample_rate,
@@ -266,6 +328,7 @@ async fn main() -> Result<(), MainError> {
discord_bot_owner_user_id,
discord_client,
discord_user_id,
discord_voice_channel_corresponding_text_channel,
recording_data,
songbird,
user_data,
@@ -344,7 +407,12 @@ async fn handle_events(command_router: Arc<CommandRouter>, state: State, mut sha
Ok(event) => {
handle_event(command_router.clone(), state.clone(), event).await;
}
Err(reconnect_error) if matches!(reconnect_error.kind(), &twilight_gateway::error::ReceiveMessageErrorType::Reconnect) => {
Err(reconnect_error)
if matches!(
reconnect_error.kind(),
&twilight_gateway::error::ReceiveMessageErrorType::Reconnect
) =>
{
tracing::error!(?reconnect_error);
return;
}