1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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<String> {
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<serenity::AutocompleteChoice> {
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<serenity::AutocompleteChoice> {
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<String> {
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![]
}
}
}
|