diff options
Diffstat (limited to 'src/commands/autocomplete.rs')
| -rw-r--r-- | src/commands/autocomplete.rs | 128 |
1 files changed, 128 insertions, 0 deletions
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 @@ | |||
| 1 | use poise::serenity_prelude as serenity; | ||
| 2 | |||
| 3 | use crate::Data; | ||
| 4 | use crate::copyparty::{html_decode, percent_decode}; | ||
| 5 | use crate::error::Error; | ||
| 6 | |||
| 7 | type Context<'a> = poise::Context<'a, Data, Error>; | ||
| 8 | |||
| 9 | const MAX_CHOICE_LEN: usize = 99; | ||
| 10 | |||
| 11 | fn filename_label(raw_path: &str) -> Option<String> { | ||
| 12 | let raw_name = raw_path.rsplit('/').next()?; | ||
| 13 | if raw_name.is_empty() { | ||
| 14 | return None; | ||
| 15 | } | ||
| 16 | let decoded = html_decode(&percent_decode(raw_name)); | ||
| 17 | if decoded.len() > MAX_CHOICE_LEN { | ||
| 18 | let mut s: String = decoded.chars().take(MAX_CHOICE_LEN - 1).collect(); | ||
| 19 | s.push('\u{2026}'); | ||
| 20 | Some(s) | ||
| 21 | } else { | ||
| 22 | Some(decoded) | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | pub async fn audio_file<'a>( | ||
| 27 | ctx: Context<'_>, | ||
| 28 | partial: &'a str, | ||
| 29 | ) -> Vec<serenity::AutocompleteChoice> { | ||
| 30 | eprintln!("[DEBUG] autocomplete audio_file(partial={partial:?})"); | ||
| 31 | let results = ctx | ||
| 32 | .data() | ||
| 33 | .copyparty | ||
| 34 | .search(partial, 25, &["mp3", "wav", "ogg", "flac", "m4a", "opus"]) | ||
| 35 | .await; | ||
| 36 | match results { | ||
| 37 | Ok(hits) => { | ||
| 38 | eprintln!("[DEBUG] audio_file got {} raw hits", hits.len()); | ||
| 39 | let mut choices = Vec::new(); | ||
| 40 | for (i, hit) in hits.into_iter().enumerate() { | ||
| 41 | let rp = match &hit.rp { | ||
| 42 | Some(p) => p.clone(), | ||
| 43 | None => continue, | ||
| 44 | }; | ||
| 45 | eprintln!( | ||
| 46 | "[DEBUG] hit[{i}]: rp={rp:?}, url={:?}, name={:?}, path={:?}", | ||
| 47 | hit.url, hit.name, hit.path | ||
| 48 | ); | ||
| 49 | let Some(label) = filename_label(&rp) else { | ||
| 50 | continue; | ||
| 51 | }; | ||
| 52 | ctx.data() | ||
| 53 | .ac_stash(ctx.author().id, label.clone(), hit) | ||
| 54 | .await; | ||
| 55 | eprintln!("[DEBUG] -> cached as {label:?}"); | ||
| 56 | choices.push(serenity::AutocompleteChoice::new(label.clone(), rp)); | ||
| 57 | if choices.len() >= 25 { | ||
| 58 | break; | ||
| 59 | } | ||
| 60 | } | ||
| 61 | eprintln!("[DEBUG] audio_file returning {} choices", choices.len()); | ||
| 62 | choices | ||
| 63 | } | ||
| 64 | Err(e) => { | ||
| 65 | eprintln!("[DEBUG] audio_file search failed: {e}"); | ||
| 66 | vec![] | ||
| 67 | } | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | pub async fn any_file<'a>(ctx: Context<'_>, partial: &'a str) -> Vec<serenity::AutocompleteChoice> { | ||
| 72 | eprintln!("[DEBUG] autocomplete any_file(partial={partial:?})"); | ||
| 73 | let results = ctx.data().copyparty.search(partial, 25, &[]).await; | ||
| 74 | match results { | ||
| 75 | Ok(hits) => { | ||
| 76 | eprintln!("[DEBUG] any_file got {} raw hits", hits.len()); | ||
| 77 | let mut choices = Vec::new(); | ||
| 78 | for (i, hit) in hits.into_iter().enumerate() { | ||
| 79 | let rp = match &hit.rp { | ||
| 80 | Some(p) => p.clone(), | ||
| 81 | None => continue, | ||
| 82 | }; | ||
| 83 | eprintln!( | ||
| 84 | "[DEBUG] hit[{i}]: rp={rp:?}, url={:?}, name={:?}, path={:?}", | ||
| 85 | hit.url, hit.name, hit.path | ||
| 86 | ); | ||
| 87 | let Some(label) = filename_label(&rp) else { | ||
| 88 | continue; | ||
| 89 | }; | ||
| 90 | ctx.data() | ||
| 91 | .ac_stash(ctx.author().id, label.clone(), hit) | ||
| 92 | .await; | ||
| 93 | eprintln!("[DEBUG] -> cached as {label:?}"); | ||
| 94 | choices.push(serenity::AutocompleteChoice::new(label.clone(), label)); | ||
| 95 | if choices.len() >= 25 { | ||
| 96 | break; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | eprintln!("[DEBUG] any_file returning {} choices", choices.len()); | ||
| 100 | choices | ||
| 101 | } | ||
| 102 | Err(e) => { | ||
| 103 | eprintln!("[DEBUG] any_file search failed: {e}"); | ||
| 104 | vec![] | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | pub async fn trigger_name<'a>(ctx: Context<'_>, partial: &'a str) -> Vec<String> { | ||
| 110 | eprintln!("[DEBUG] autocomplete trigger_name(partial={partial:?})"); | ||
| 111 | let db_pool = &ctx.data().db_pool; | ||
| 112 | match crate::database::list_triggers(db_pool).await { | ||
| 113 | Ok(triggers) => { | ||
| 114 | let names: Vec<_> = triggers | ||
| 115 | .into_iter() | ||
| 116 | .map(|t| t.name) | ||
| 117 | .filter(|name| partial.is_empty() || name.starts_with(partial)) | ||
| 118 | .take(25) | ||
| 119 | .collect(); | ||
| 120 | eprintln!("[DEBUG] trigger_name returning {} names", names.len()); | ||
| 121 | names | ||
| 122 | } | ||
| 123 | Err(e) => { | ||
| 124 | eprintln!("[DEBUG] trigger_name query failed: {e}"); | ||
| 125 | vec![] | ||
| 126 | } | ||
| 127 | } | ||
| 128 | } | ||
