Compare commits

..

2 Commits

5 changed files with 129 additions and 19 deletions

27
Cargo.lock generated
View File

@@ -1751,12 +1751,14 @@ dependencies = [
"futures", "futures",
"hound", "hound",
"opendal", "opendal",
"opus2",
"patricia_tree 0.10.1", "patricia_tree 0.10.1",
"rhai", "rhai",
"rustls 0.23.35", "rustls 0.23.35",
"secrecy 0.10.3", "secrecy 0.10.3",
"snafu", "snafu",
"songbird", "songbird",
"strum 0.28.0",
"time", "time",
"tokio", "tokio",
"tokio-util", "tokio-util",
@@ -2321,7 +2323,7 @@ dependencies = [
"hex", "hex",
"shorthand", "shorthand",
"stable-vec", "stable-vec",
"strum", "strum 0.17.1",
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
@@ -6547,7 +6549,16 @@ version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "530efb820d53b712f4e347916c5e7ed20deb76a4f0457943b3182fb889b06d2c" checksum = "530efb820d53b712f4e347916c5e7ed20deb76a4f0457943b3182fb889b06d2c"
dependencies = [ dependencies = [
"strum_macros", "strum_macros 0.17.1",
]
[[package]]
name = "strum"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
dependencies = [
"strum_macros 0.28.0",
] ]
[[package]] [[package]]
@@ -6562,6 +6573,18 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "strum_macros"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.111",
]
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.6.1" version = "2.6.1"

View File

@@ -49,6 +49,7 @@ opendal = { git = "https://github.com/apache/opendal", features = [
"services-sled", "services-sled",
"services-webdav", "services-webdav",
] } ] }
opus2 = "0.4.0"
patricia_tree = "0.10.1" patricia_tree = "0.10.1"
rhai = "1.23.6" rhai = "1.23.6"
rustls = "0.23" rustls = "0.23"
@@ -62,6 +63,7 @@ songbird = { version = "0.6.0", default-features = false, features = [
"twilight", "twilight",
"tws", "tws",
] } ] }
strum = { version = "0.28.0", features = ["derive"] }
time = "0.3.47" time = "0.3.47"
tokio = { version = "1.46.0", features = ["rt-multi-thread", "macros", "signal"] } tokio = { version = "1.46.0", features = ["rt-multi-thread", "macros", "signal"] }
tokio-util = "0.7.18" tokio-util = "0.7.18"

View File

@@ -99,25 +99,23 @@ struct Handler {
guild_id: Id<GuildMarker>, guild_id: Id<GuildMarker>,
channel_id: Id<ChannelMarker>, channel_id: Id<ChannelMarker>,
vcs: Arc<VCs>,
known_ssrcs: Arc<Mutex<OneToManyUniqueBTreeMap<Id<UserMarker>, u32>>>, known_ssrcs: Arc<Mutex<OneToManyUniqueBTreeMap<Id<UserMarker>, u32>>>,
audio_channels: u16,
audio_sample_rate: u32,
} }
#[async_trait] #[async_trait]
impl EventHandler for Handler { impl EventHandler for Handler {
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> { async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
let elapsed = self.start_instant.elapsed();
let elapsed = elapsed.try_into().expect("TODO");
let now_utc = self.start_utc.checked_add(elapsed).expect("TODO");
tracing::error!(?now_utc, "TODO");
match ctx { match ctx {
EventContext::Track(_items) => { EventContext::Track(_items) => {
// Not expected to fire // Not expected to fire
} }
EventContext::SpeakingStateUpdate(speaking) => { EventContext::SpeakingStateUpdate(speaking) => {
tracing::error!(?speaking);
if let Some(user_id) = speaking.user_id { if let Some(user_id) = speaking.user_id {
let user_id = Id::new(user_id.0); let user_id = Id::new(user_id.0);
@@ -128,10 +126,20 @@ impl EventHandler for Handler {
} }
} }
EventContext::VoiceTick(voice_tick) => { EventContext::VoiceTick(voice_tick) => {
tracing::error!(?voice_tick);
for (ssrc, voice_data) in &voice_tick.speaking { for (ssrc, voice_data) in &voice_tick.speaking {
let user_id = self.known_ssrcs.lock().unwrap().get_left_for(ssrc).cloned(); let user_id = self.known_ssrcs.lock().unwrap().get_left_for(ssrc).cloned();
tracing::info!(?user_id);
if let Some(pcm) = &voice_data.decoded_voice { if let Some(pcm) = &voice_data.decoded_voice {
let elapsed = self.start_instant.elapsed();
let elapsed = elapsed.try_into().expect("TODO");
let now_utc = self.start_utc.checked_add(elapsed).expect("TODO");
tracing::error!(?now_utc, "TODO");
let year = now_utc.year(); let year = now_utc.year();
let month = now_utc.month(); let month = now_utc.month();
let day = now_utc.day(); let day = now_utc.day();
@@ -145,15 +153,20 @@ impl EventHandler for Handler {
let guild_id = self.guild_id; let guild_id = self.guild_id;
let channel_id = self.channel_id; let channel_id = self.channel_id;
let user = user_id.map_or_else(|| "Unknown".into(), ToString::to_string); let user = user_id
.as_ref()
.map_or_else(|| "UNKNOWN".into(), ToString::to_string);
let path = format!( let path = format!(
"{year}/{month}/{day}/{hour}/{minute}/audio-{second}.{microseconds}-{guild_id}-{channel_id}-{user}.wav" "{year}/{month}/{day}/{hour}/{minute}/audio-{second}.{microseconds}-{guild_id}-{channel_id}-{user}.wav"
); );
let channels = self.audio_channels;
let sample_rate = self.audio_sample_rate;
let wav_spec = WavSpec { let wav_spec = WavSpec {
channels: 2, channels,
sample_rate: 48000, sample_rate,
bits_per_sample: 16, bits_per_sample: 16,
sample_format: SampleFormat::Int, sample_format: SampleFormat::Int,
}; };
@@ -254,20 +267,26 @@ pub async fn handle(state: State, interaction: Interaction) {
let start_instant = Instant::now(); let start_instant = Instant::now();
let start_utc = UtcDateTime::now(); let start_utc = UtcDateTime::now();
let audio_channels = opus2::Channels::from(state.audio_channels) as u16;
let audio_sample_rate = u32::from(state.audio_sample_rate);
let handler = Handler { let handler = Handler {
start_instant, start_instant,
start_utc, start_utc,
recordings: state.recording_data, recordings: state.recording_data,
guild_id, guild_id,
channel_id: voice_channel_id, channel_id: voice_channel_id,
vcs,
known_ssrcs: Default::default(), known_ssrcs: Default::default(),
audio_channels,
audio_sample_rate,
}; };
{ {
let call = call.lock().await; let mut call = call.lock().await;
call.add_global_event(CoreEvent::SpeakingStateUpdate.into(), handler); call.add_global_event(CoreEvent::SpeakingStateUpdate.into(), handler.clone());
call.add_global_event(CoreEvent::VoiceTick.into(), handler); call.add_global_event(CoreEvent::VoiceTick.into(), handler);
} }
@@ -291,7 +310,7 @@ pub async fn handle(state: State, interaction: Interaction) {
EmbedFieldBuilder::new("You won't be \"audited\"", "I will not reference things said in past recordings with the goal of \"making a point\", nor pull them up on the spot (even by the request of the person who said it). Ideally, these are just peace of mind for me that I'm not missing out by not being in a Discord call all the time and can take my life back, so using them in an unhealthy way isn't in my interest.").build() EmbedFieldBuilder::new("You won't be \"audited\"", "I will not reference things said in past recordings with the goal of \"making a point\", nor pull them up on the spot (even by the request of the person who said it). Ideally, these are just peace of mind for me that I'm not missing out by not being in a Discord call all the time and can take my life back, so using them in an unhealthy way isn't in my interest.").build()
) )
.field( .field(
EmbedFieldBuilder::new("Code is publically available", "The latest source code is at https://gitea.katniss.top/jacob/fomo-reducer so that I don't have to write guarantees about the technology here (e.g. what data is acquired, how it's used or stored) and you can just check it yourself").build() EmbedFieldBuilder::new("Code is publicly available", "The latest source code is at https://gitea.katniss.top/jacob/fomo-reducer so that I don't have to write guarantees about the technology here (e.g. what data is acquired, how it's used or stored) and you can just check it yourself.").build()
) )
.footer( .footer(
EmbedFooterBuilder::new("Thanks for your patience and understanding as I have bad and unusual mental health and it's crazy that I need this. This - especially if I learn if I can record streams or webcams so I don't miss out on those experiences either - should be the end of abrasion and force about how we spend our time. Again, thank you, I appreciate it.") EmbedFooterBuilder::new("Thanks for your patience and understanding as I have bad and unusual mental health and it's crazy that I need this. This - especially if I learn if I can record streams or webcams so I don't miss out on those experiences either - should be the end of abrasion and force about how we spend our time. Again, thank you, I appreciate it.")

View File

@@ -3,7 +3,7 @@ use std::{fmt::Debug, sync::Arc};
use futures::future::BoxFuture; use futures::future::BoxFuture;
use opendal::Operator; use opendal::Operator;
use patricia_tree::StringPatriciaMap; use patricia_tree::StringPatriciaMap;
use songbird::Songbird; use songbird::{Songbird, driver::{Channels, SampleRate}};
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use twilight_model::{ use twilight_model::{
application::{command::Command, interaction::Interaction}, application::{command::Command, interaction::Interaction},
@@ -23,6 +23,8 @@ mod opt_out;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct State { pub struct State {
pub audio_channels: Channels,
pub audio_sample_rate: SampleRate,
pub bot_data: Operator, pub bot_data: Operator,
pub cancellation_token: CancellationToken, pub cancellation_token: CancellationToken,
pub discord_application_id: Id<ApplicationMarker>, pub discord_application_id: Id<ApplicationMarker>,

View File

@@ -2,8 +2,13 @@ use clap::Parser;
use fomo_reducer::{CommandRouter, State, Storage, all_commands, initialize_vcs, update_vcs}; use fomo_reducer::{CommandRouter, State, Storage, all_commands, initialize_vcs, update_vcs};
use secrecy::{ExposeSecret, SecretString}; use secrecy::{ExposeSecret, SecretString};
use snafu::Snafu; use snafu::Snafu;
use songbird::{Songbird, shards::TwilightMap}; use songbird::{
Config, Songbird,
driver::{Channels, DecodeConfig, SampleRate},
shards::TwilightMap,
};
use std::{fmt::Debug, sync::Arc}; use std::{fmt::Debug, sync::Arc};
use strum::EnumString;
use tokio::{select, signal::ctrl_c, task::JoinSet}; use tokio::{select, signal::ctrl_c, task::JoinSet};
use tokio_util::{sync::CancellationToken, time::FutureExt as _}; use tokio_util::{sync::CancellationToken, time::FutureExt as _};
use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan}; use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan};
@@ -17,6 +22,47 @@ use twilight_model::{
id::{Id, marker::UserMarker}, id::{Id, marker::UserMarker},
}; };
#[derive(Clone, Copy, Debug, strum::Display, EnumString)]
enum AudioChannels {
Mono,
Stereo,
}
impl From<AudioChannels> for Channels {
fn from(value: AudioChannels) -> Self {
match value {
AudioChannels::Mono => Channels::Mono,
AudioChannels::Stereo => Channels::Stereo,
}
}
}
#[derive(Clone, Copy, Debug, strum::Display, EnumString)]
enum AudioSampleRate {
#[strum(serialize = "8000Hz")]
Hz8000,
#[strum(serialize = "12000Hz")]
Hz12000,
#[strum(serialize = "16000Hz")]
Hz16000,
#[strum(serialize = "24000Hz")]
Hz24000,
#[strum(serialize = "48000Hz")]
Hz48000,
}
impl From<AudioSampleRate> for SampleRate {
fn from(value: AudioSampleRate) -> Self {
match value {
AudioSampleRate::Hz8000 => SampleRate::Hz8000,
AudioSampleRate::Hz12000 => SampleRate::Hz12000,
AudioSampleRate::Hz16000 => SampleRate::Hz16000,
AudioSampleRate::Hz24000 => SampleRate::Hz24000,
AudioSampleRate::Hz48000 => SampleRate::Hz48000,
}
}
}
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
struct AppArgs { struct AppArgs {
#[arg(long, env)] #[arg(long, env)]
@@ -31,6 +77,12 @@ struct AppArgs {
#[arg(long, env)] #[arg(long, env)]
discord_status: Option<Arc<str>>, discord_status: Option<Arc<str>>,
#[arg(long, env, default_value_t = AudioChannels::Mono)]
audio_channels: AudioChannels,
#[arg(long, env, default_value_t = AudioSampleRate::Hz12000)]
audio_sample_rate: AudioSampleRate,
#[arg(long, env)] #[arg(long, env)]
bot_data: Storage, bot_data: Storage,
@@ -89,6 +141,8 @@ async fn main() -> Result<(), MainError> {
discord_bot_owner_user_id, discord_bot_owner_user_id,
discord_nickname, discord_nickname,
discord_status, discord_status,
audio_channels,
audio_sample_rate,
bot_data, bot_data,
user_data, user_data,
recording_data, recording_data,
@@ -159,9 +213,17 @@ async fn main() -> Result<(), MainError> {
.collect(), .collect(),
); );
let senders = Arc::new(senders); let audio_channels = audio_channels.into();
let audio_sample_rate = audio_sample_rate.into();
let senders = Arc::new(senders);
let songbird = Songbird::twilight(senders, discord_user_id); let songbird = Songbird::twilight(senders, discord_user_id);
songbird.set_config(
Config::default().decode_mode(songbird::driver::DecodeMode::Decode(DecodeConfig::new(
audio_channels,
audio_sample_rate,
))),
);
let interaction_client = discord_client.interaction(discord_application_id); let interaction_client = discord_client.interaction(discord_application_id);
@@ -196,6 +258,8 @@ async fn main() -> Result<(), MainError> {
let user_data = user_data.into_inner(); let user_data = user_data.into_inner();
let state = State { let state = State {
audio_channels,
audio_sample_rate,
bot_data, bot_data,
cancellation_token: cancellation_token.clone(), cancellation_token: cancellation_token.clone(),
discord_application_id, discord_application_id,