Skip to main content

stoat/commands/
handler.rs

1use crate::{
2    Context as MessageContext, Error,
3    commands::{
4        Check, Command, CommandEventHandler, Context, DefaultHelpCommand, HelpCommand, Words,
5        help_command,
6    },
7};
8use state::TypeMap;
9use std::{
10    collections::HashMap,
11    fmt::Debug,
12    sync::{Arc, RwLock},
13};
14use stoat_models::v0::Message;
15
16#[derive(Clone)]
17pub struct CommandHandler<H: CommandEventHandler + Clone + Send + Sync + 'static> {
18    commands: Commands<H::Error, H::State>,
19    checks: Vec<Arc<dyn Check<H::Error, H::State>>>,
20    event_handler: H,
21    state: H::State,
22    help_command: Arc<dyn HelpCommand<H::Error, H::State>>,
23}
24
25impl<
26    H: CommandEventHandler<State = S, Error = E> + Clone + Send + Sync,
27    E: From<Error> + Clone + Debug + Send + Sync + 'static,
28    S: Debug + Clone + Send + Sync + 'static,
29> CommandHandler<H>
30{
31    pub fn new(event_handler: H, state: S) -> Self {
32        let commands = Commands::new();
33        commands.register(help_command());
34
35        Self {
36            commands,
37            checks: Vec::new(),
38            event_handler,
39            state,
40            help_command: Arc::new(DefaultHelpCommand),
41        }
42    }
43
44    pub fn register(self, commands: Vec<Command<E, S>>) -> Self {
45        for command in commands {
46            self.commands.register(command)
47        }
48
49        self
50    }
51
52    pub fn help_command<HC: HelpCommand<E, S> + 'static>(
53        mut self,
54        help_command: Option<HC>,
55    ) -> Self {
56        if let Some(help_command) = help_command {
57            self.help_command = Arc::new(help_command);
58        } else if let Some(help_command) = self.commands.get_command("help") {
59            self.commands.unregister(help_command);
60        }
61
62        self
63    }
64
65    pub fn check<C: Check<E, S>>(mut self, check: C) -> Self {
66        self.checks.push(Arc::new(check));
67
68        self
69    }
70
71    pub async fn can_run(&self, context: Context<E, S>) -> Result<bool, E> {
72        for check in &self.checks {
73            if check.run(context.clone()).await? == false {
74                return Err(Error::CheckFailure.into());
75            }
76        }
77
78        if let Some(command) = &context.command {
79            return command.can_run(context.clone()).await;
80        }
81
82        Ok(true)
83    }
84
85    pub async fn process_commands(
86        &self,
87        context: MessageContext,
88        message: Message,
89    ) -> Result<(), E> {
90        let Some(message_content) = message.content.as_deref() else {
91            // no content
92            return Ok(());
93        };
94
95        if message.user.as_ref().unwrap().bot.is_some() {
96            return Ok(());
97        };
98
99        let mut cmd_context = Context {
100            inner: context,
101            prefix: None,
102            command: None,
103            message: message.clone(),
104            state: self.state.clone(),
105            words: Words::new(message_content),
106            commands: self.commands.clone(),
107            help_command: self.help_command.clone(),
108            local_state: Arc::new(<TypeMap![Send + Sync]>::new()),
109        };
110
111        let prefixes = match self.event_handler.get_prefix(cmd_context.clone()).await {
112            Ok(prefixes) => prefixes,
113            Err(e) => {
114                self.event_handler.error(cmd_context.clone(), e).await?;
115                return Ok(());
116            }
117        };
118
119        let Some(prefix) = prefixes
120            .into_iter()
121            .filter(|prefix| message_content.starts_with(prefix))
122            .next()
123        else {
124            // doesnt start with prefix
125            return Ok(());
126        };
127
128        let rest = &message_content[prefix.len()..];
129
130        cmd_context.words = Words::new(rest);
131        cmd_context.command = self
132            .commands
133            .find_command_from_words(None, &cmd_context.words);
134        cmd_context.prefix = Some(prefix);
135
136        if cmd_context.command.is_none() {
137            if let Err(e) = self.event_handler.no_command(cmd_context.clone()).await {
138                self.event_handler.error(cmd_context.clone(), e).await?;
139            };
140
141            return Ok(());
142        }
143
144        if let Err(e) = self.event_handler.command(cmd_context.clone()).await {
145            self.event_handler.error(cmd_context.clone(), e).await?;
146        };
147
148        if let Some(command) = cmd_context.command.as_ref() {
149            if let Err(e) = self.can_run(cmd_context.clone()).await {
150                self.event_handler.error(cmd_context.clone(), e).await?;
151            } else {
152                if let Err(e) = command.handle.handle(cmd_context.clone()).await {
153                    if let Some(error) = &command.error {
154                        error.handle(cmd_context.clone(), e.clone()).await?;
155                    };
156
157                    self.event_handler.error(cmd_context.clone(), e).await?;
158                };
159
160                if let Err(e) = self.event_handler.after_command(cmd_context.clone()).await {
161                    self.event_handler.error(cmd_context.clone(), e).await?;
162                };
163            }
164        }
165
166        Ok(())
167    }
168}
169
170#[derive(Debug, Clone)]
171pub struct Commands<
172    E: From<Error> + Clone + Debug + Send + Sync + 'static,
173    S: Debug + Clone + Send + Sync + 'static,
174> {
175    mapping: Arc<RwLock<HashMap<String, Command<E, S>>>>,
176}
177
178impl<
179    E: From<Error> + Clone + Debug + Send + Sync + 'static,
180    S: Debug + Clone + Send + Sync + 'static,
181> Commands<E, S>
182{
183    pub fn new() -> Self {
184        Self {
185            mapping: Arc::new(RwLock::new(HashMap::new())),
186        }
187    }
188
189    pub fn register(&self, command: Command<E, S>) {
190        let mut mapping = self.mapping.write().unwrap();
191
192        mapping.insert(command.name.clone(), command.clone());
193
194        for alias in command.aliases.clone() {
195            mapping.insert(alias, command.clone());
196        }
197    }
198
199    pub fn unregister(&self, command: Command<E, S>) {
200        let mut mapping = self.mapping.write().unwrap();
201
202        mapping.remove(&command.name);
203
204        for alias in command.aliases.clone() {
205            mapping.remove(&alias);
206        }
207    }
208
209    pub fn find_command_from_words(
210        &self,
211        current_command: Option<&Command<E, S>>,
212        words: &Words,
213    ) -> Option<Command<E, S>> {
214        let next_word = words.current()?;
215
216        let commands = self.mapping.read().unwrap();
217
218        if let Some(command) = current_command
219            .and_then(|command| command.children.get(&next_word))
220            .or_else(|| commands.get(&next_word))
221        {
222            words.advance();
223
224            if !command.children.is_empty() {
225                let subcommand = self.find_command_from_words(Some(command), words);
226
227                match subcommand {
228                    Some(sub) => Some(sub),
229                    None => {
230                        // words.undo();
231
232                        Some(command.clone())
233                    }
234                }
235            } else {
236                Some(command.clone())
237            }
238        } else {
239            None
240        }
241    }
242
243    pub fn get_command_from_slice(&self, words: &[String]) -> Option<Command<E, S>> {
244        let mapping = self.mapping.read().unwrap();
245
246        let mut current_command: Option<Command<E, S>> = None;
247
248        for word in words {
249            if let Some(command) = current_command
250                .as_ref()
251                .and_then(|command| command.get_command(word))
252                .or_else(|| mapping.get(word).cloned())
253            {
254                current_command = Some(command)
255            } else {
256                break;
257            }
258        }
259
260        return current_command;
261    }
262
263    pub fn get_command(&self, name: &str) -> Option<Command<E, S>> {
264        self.mapping.read().unwrap().get(name).cloned()
265    }
266
267    pub fn get_commands(&self) -> Vec<Command<E, S>> {
268        self.mapping
269            .read()
270            .unwrap()
271            .clone()
272            .into_iter()
273            .filter(|(name, command)| name == &command.name)
274            .map(|(_, command)| command)
275            .collect()
276    }
277
278    pub fn get_command_parents(&self, command: &Command<E, S>) -> Vec<Command<E, S>> {
279        let mut parents: Vec<Command<E, S>> = Vec::new();
280
281        for parent in &command.parents {
282            if let Some(last_parent) = parents.last() {
283                let child = last_parent.get_command(parent).unwrap();
284                parents.push(child);
285            } else {
286                parents.push(self.get_command(parent).unwrap());
287            }
288        }
289
290        parents
291    }
292}