aboutsummaryrefslogtreecommitdiffstats
path: root/src/commands/autocomplete.rs
blob: 497aec9ebd9e9d97dc6e18a0fef13b2a672872ce (plain) (blame)
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![]
        }
    }
}