From 3f1a4d113e71fc412ec05a319d5ffd5a3d7a78bc Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 31 Jul 2026 01:09:07 +0200 Subject: fir auto completion and send file in the response body upon grajcuj file --- src/commands/music.rs | 36 ++++++++++++++++++++++++++++++++++-- src/events/mod.rs | 29 +++++++++++++++++++---------- src/main.rs | 2 +- src/voice/mod.rs | 20 +++++++++++++------- 4 files changed, 67 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/commands/music.rs b/src/commands/music.rs index 05abb9c..6acac00 100644 --- a/src/commands/music.rs +++ b/src/commands/music.rs @@ -253,6 +253,7 @@ async fn play_impl( }) .ok_or_else(lang::err_not_in_voice)?; + let file_reupload = file.as_ref().map(|f| (f.url.clone(), f.filename.clone())); let src = resolve_source(ctx, melodia, file).await?; let call; @@ -296,7 +297,7 @@ async fn play_impl( .lock() .await .insert(guild_id.get(), handle); - ctx.say(lang::fmt_music_playing(&src.display_name)).await?; + send_music_msg(ctx, &src.display_name, file_reupload).await?; return Ok(()); } info!(url = %redact_url(&src.url), "downloading for compression"); @@ -365,7 +366,7 @@ async fn play_impl( .await .insert(guild_id.get(), handle); - ctx.say(lang::fmt_music_playing(&src.display_name)).await?; + send_music_msg(ctx, &src.display_name, file_reupload).await?; Ok(()) } @@ -412,6 +413,37 @@ pub async fn join(ctx: Context<'_>) -> Result<()> { Ok(()) } +/// Send the "playing" message, optionally reuploading a user-provided file +/// so other channel members can download it. +async fn send_music_msg( + ctx: Context<'_>, + display_name: &str, + file_reupload: Option<(String, String)>, +) -> Result<()> { + if let Some((url, filename)) = file_reupload { + match ctx.data().http_client.get(&url).send().await { + Ok(resp) => match resp.bytes().await { + Ok(bytes) => { + if ctx + .send(poise::CreateReply::default().content(lang::fmt_music_playing(display_name)).attachment( + serenity::CreateAttachment::bytes(bytes.to_vec(), filename.clone()), + )) + .await + .is_ok() + { + return Ok(()); + } + error!("send_music_msg: ctx.send with attachment failed, falling back"); + } + Err(e) => error!("send_music_msg: failed to read file bytes: {e}"), + }, + Err(e) => error!("send_music_msg: failed to download file: {e}"), + } + } + ctx.say(lang::fmt_music_playing(display_name)).await?; + Ok(()) +} + pub fn commands() -> Vec> { vec![grajcuj(), stop(), join()] } diff --git a/src/events/mod.rs b/src/events/mod.rs index d166993..82b6f19 100644 --- a/src/events/mod.rs +++ b/src/events/mod.rs @@ -130,8 +130,7 @@ pub async fn handle_voice_state_update( let mut tts_channel: Option = None; // ── JOIN ── - if old_channel.is_none() && new_channel.is_some() { - let cid = new_channel.unwrap(); + if old_channel.is_none() && let Some(cid) = new_channel { let is_afk = new.self_mute || new.self_deaf; va.join_time = now; @@ -332,11 +331,18 @@ pub async fn handle_message( let has_keyword = content_lower.contains(&data.config.assistant_keyword); if is_mention || has_keyword { + // Spawn the LLM call in a background task so it doesn't block the + // event loop. Discord interactions must be answered within 3 s; a + // 5–10 s OpenRouter call would make every subsequent interaction + // expire with "Unknown interaction". + let http = ctx.http.clone(); + let channel_id = new_message.channel_id; + let _reply_to_id = new_message.id; + let bot_id = ctx.cache.current_user().id; let (api_url, api_key, model) = data.config.resolve_llm(); - let client = crate::openrouter::OpenRouterClient::new(api_url, api_key, model); - let assistant_prompt = lang::ASSISTANT_SYSTEM_PROMPT; + let assistant_prompt = lang::ASSISTANT_SYSTEM_PROMPT.to_string(); - let bot_id = ctx.cache.current_user().id; + // Build message history synchronously (fast path). let mut messages = Vec::new(); let mut current_msg = Some(new_message.clone()); for _ in 0..5 { @@ -346,9 +352,9 @@ pub async fn handle_message( } else { "user" }; - messages.push((role.into(), msg.content.clone())); + messages.push((role.to_string(), msg.content.clone())); current_msg = match msg.message_reference.and_then(|r| r.message_id) { - Some(ref_id) => msg.channel_id.message(ctx, ref_id).await.ok(), + Some(ref_id) => channel_id.message(&http, ref_id).await.ok(), None => None, }; } else { @@ -357,9 +363,12 @@ pub async fn handle_message( } messages.reverse(); - if let Ok(response) = client.chat_with_history(assistant_prompt, messages).await { - new_message.reply(ctx, response).await.ok(); - } + tokio::spawn(async move { + let client = crate::openrouter::OpenRouterClient::new(api_url, api_key, model); + if let Ok(response) = client.chat_with_history(&assistant_prompt, messages).await { + let _ = channel_id.say(&http, response).await; + } + }); } Ok(()) diff --git a/src/main.rs b/src/main.rs index 7874c86..d06176d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -159,7 +159,7 @@ async fn main() -> Result<(), Box> { let copyparty = copyparty.clone(); let triggers = triggers.clone(); Box::pin(async move { - let vm = voice::VoiceManager::new(ctx).await; + let vm = voice::VoiceManager::new(ctx).await?; let data = Data { config, diff --git a/src/voice/mod.rs b/src/voice/mod.rs index 0d5b89e..4087eba 100644 --- a/src/voice/mod.rs +++ b/src/voice/mod.rs @@ -25,12 +25,13 @@ pub struct VoiceManager { } impl VoiceManager { - pub async fn new(ctx: &serenity::client::Context) -> Self { - Self { - songbird: songbird::get(ctx) - .await - .expect("Songbird not registered via register_songbird()"), - } + pub async fn new(ctx: &serenity::client::Context) -> Result { + let songbird = songbird::get(ctx) + .await + .ok_or_else(|| Error::Custom( + "Songbird not registered via register_songbird()".into(), + ))?; + Ok(Self { songbird }) } pub async fn connect( @@ -54,7 +55,12 @@ impl VoiceManager { warn!("Driver error details: {driver_err:?}"); } last_err = Some(e); - tokio::time::sleep(Duration::from_secs(1)).await; + + // Stale voice state: remove the guild from songbird and + // wait a few seconds so Discord's voice gateway can fully + // process the disconnect before we retry. + let _ = self.songbird.remove(guild_id).await; + tokio::time::sleep(Duration::from_secs(3)).await; } } } -- cgit v1.2.3