From cd8ed8bc5793985df2c1692072a932ceee517b95 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 8 Jul 2026 02:13:35 +0200 Subject: OPEN THE RUST GATES Signed-off-by: salmonik --- src/commands/autocomplete.rs | 128 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 src/commands/autocomplete.rs (limited to 'src/commands/autocomplete.rs') diff --git a/src/commands/autocomplete.rs b/src/commands/autocomplete.rs new file mode 100644 index 0000000..497aec9 --- /dev/null +++ b/src/commands/autocomplete.rs @@ -0,0 +1,128 @@ +use poise::serenity_prelude as serenity; + +use crate::Data; +use crate::copyparty::{html_decode, percent_decode}; +use crate::error::Error; + +type Context<'a> = poise::Context<'a, Data, Error>; + +const MAX_CHOICE_LEN: usize = 99; + +fn filename_label(raw_path: &str) -> Option { + let raw_name = raw_path.rsplit('/').next()?; + if raw_name.is_empty() { + return None; + } + let decoded = html_decode(&percent_decode(raw_name)); + if decoded.len() > MAX_CHOICE_LEN { + let mut s: String = decoded.chars().take(MAX_CHOICE_LEN - 1).collect(); + s.push('\u{2026}'); + Some(s) + } else { + Some(decoded) + } +} + +pub async fn audio_file<'a>( + ctx: Context<'_>, + partial: &'a str, +) -> Vec { + eprintln!("[DEBUG] autocomplete audio_file(partial={partial:?})"); + let results = ctx + .data() + .copyparty + .search(partial, 25, &["mp3", "wav", "ogg", "flac", "m4a", "opus"]) + .await; + match results { + Ok(hits) => { + eprintln!("[DEBUG] audio_file got {} raw hits", hits.len()); + let mut choices = Vec::new(); + for (i, hit) in hits.into_iter().enumerate() { + let rp = match &hit.rp { + Some(p) => p.clone(), + None => continue, + }; + eprintln!( + "[DEBUG] hit[{i}]: rp={rp:?}, url={:?}, name={:?}, path={:?}", + hit.url, hit.name, hit.path + ); + let Some(label) = filename_label(&rp) else { + continue; + }; + ctx.data() + .ac_stash(ctx.author().id, label.clone(), hit) + .await; + eprintln!("[DEBUG] -> cached as {label:?}"); + choices.push(serenity::AutocompleteChoice::new(label.clone(), rp)); + if choices.len() >= 25 { + break; + } + } + eprintln!("[DEBUG] audio_file returning {} choices", choices.len()); + choices + } + Err(e) => { + eprintln!("[DEBUG] audio_file search failed: {e}"); + vec![] + } + } +} + +pub async fn any_file<'a>(ctx: Context<'_>, partial: &'a str) -> Vec { + eprintln!("[DEBUG] autocomplete any_file(partial={partial:?})"); + let results = ctx.data().copyparty.search(partial, 25, &[]).await; + match results { + Ok(hits) => { + eprintln!("[DEBUG] any_file got {} raw hits", hits.len()); + let mut choices = Vec::new(); + for (i, hit) in hits.into_iter().enumerate() { + let rp = match &hit.rp { + Some(p) => p.clone(), + None => continue, + }; + eprintln!( + "[DEBUG] hit[{i}]: rp={rp:?}, url={:?}, name={:?}, path={:?}", + hit.url, hit.name, hit.path + ); + let Some(label) = filename_label(&rp) else { + continue; + }; + ctx.data() + .ac_stash(ctx.author().id, label.clone(), hit) + .await; + eprintln!("[DEBUG] -> cached as {label:?}"); + choices.push(serenity::AutocompleteChoice::new(label.clone(), label)); + if choices.len() >= 25 { + break; + } + } + eprintln!("[DEBUG] any_file returning {} choices", choices.len()); + choices + } + Err(e) => { + eprintln!("[DEBUG] any_file search failed: {e}"); + vec![] + } + } +} + +pub async fn trigger_name<'a>(ctx: Context<'_>, partial: &'a str) -> Vec { + eprintln!("[DEBUG] autocomplete trigger_name(partial={partial:?})"); + let db_pool = &ctx.data().db_pool; + match crate::database::list_triggers(db_pool).await { + Ok(triggers) => { + let names: Vec<_> = triggers + .into_iter() + .map(|t| t.name) + .filter(|name| partial.is_empty() || name.starts_with(partial)) + .take(25) + .collect(); + eprintln!("[DEBUG] trigger_name returning {} names", names.len()); + names + } + Err(e) => { + eprintln!("[DEBUG] trigger_name query failed: {e}"); + vec![] + } + } +} -- cgit v1.2.3