Skip to main content

aster/
slash_commands.rs

1use std::path::PathBuf;
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use tracing::warn;
6
7use crate::config::Config;
8use crate::recipe::Recipe;
9
10const SLASH_COMMANDS_CONFIG_KEY: &str = "slash_commands";
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct SlashCommandMapping {
14    pub command: String,
15    pub recipe_path: String,
16}
17
18pub fn list_commands() -> Vec<SlashCommandMapping> {
19    Config::global()
20        .get_param(SLASH_COMMANDS_CONFIG_KEY)
21        .unwrap_or_else(|err| {
22            warn!(
23                "Failed to load {}: {}. Falling back to empty list.",
24                SLASH_COMMANDS_CONFIG_KEY, err
25            );
26            Vec::new()
27        })
28}
29
30fn save_slash_commands(commands: Vec<SlashCommandMapping>) -> Result<()> {
31    Config::global()
32        .set_param(SLASH_COMMANDS_CONFIG_KEY, &commands)
33        .map_err(|e| anyhow::anyhow!("Failed to save slash commands: {}", e))
34}
35
36pub fn set_recipe_slash_command(recipe_path: PathBuf, command: Option<String>) -> Result<()> {
37    let recipe_path_str = recipe_path.to_string_lossy().to_string();
38
39    let mut commands = list_commands();
40    commands.retain(|mapping| mapping.recipe_path != recipe_path_str);
41
42    if let Some(cmd) = command {
43        let normalized_cmd = cmd.trim_start_matches('/').to_lowercase();
44        if !normalized_cmd.is_empty() {
45            commands.push(SlashCommandMapping {
46                command: normalized_cmd,
47                recipe_path: recipe_path_str,
48            });
49        }
50    }
51
52    save_slash_commands(commands)
53}
54
55pub fn get_recipe_for_command(command: &str) -> Option<PathBuf> {
56    let normalized = command.trim_start_matches('/').to_lowercase();
57    let commands = list_commands();
58    commands
59        .into_iter()
60        .find(|mapping| mapping.command == normalized)
61        .map(|mapping| PathBuf::from(mapping.recipe_path))
62}
63
64pub fn resolve_slash_command(command: &str) -> Option<Recipe> {
65    let recipe_path = get_recipe_for_command(command)?;
66
67    if !recipe_path.exists() {
68        return None;
69    }
70    let recipe_content = std::fs::read_to_string(&recipe_path).ok()?;
71    let recipe = Recipe::from_content(&recipe_content).ok()?;
72
73    Some(recipe)
74}