Skip to main content

stoat/commands/
help.rs

1use std::fmt::Debug;
2
3use async_trait::async_trait;
4use stoat_models::v0::Message;
5
6use crate::{
7    Error,
8    builders::SendMessageBuilder,
9    commands::{Command, Context, Converter, command::CommandHandle},
10};
11
12#[async_trait]
13pub trait HelpCommand<
14    E: From<Error> + Clone + Debug + Send + Sync + 'static,
15    S: Debug + Clone + Send + Sync + 'static,
16>: Debug + Send + Sync
17{
18    async fn create_global_help(
19        &self,
20        context: Context<E, S>,
21        commands: Vec<Command<E, S>>,
22        builder: &mut SendMessageBuilder,
23    ) -> Result<(), E>;
24    async fn create_command_help(
25        &self,
26        context: Context<E, S>,
27        command: Command<E, S>,
28        builder: &mut SendMessageBuilder,
29    ) -> Result<(), E>;
30    async fn create_group_help(
31        &self,
32        context: Context<E, S>,
33        command: Command<E, S>,
34        builder: &mut SendMessageBuilder,
35    ) -> Result<(), E>;
36
37    async fn filter_commands(
38        &self,
39        context: Context<E, S>,
40        commands: Vec<Command<E, S>>,
41    ) -> Result<Vec<Command<E, S>>, E> {
42        let mut filtered = Vec::new();
43
44        for command in commands {
45            if command.hidden {
46                continue;
47            };
48
49            if command.can_run(context.clone()).await.is_ok_and(|b| b) {
50                filtered.push(command);
51            };
52        }
53
54        Ok(filtered)
55    }
56
57    #[allow(unused_variables)]
58    async fn send_help_command(
59        &self,
60        context: Context<E, S>,
61        builder: SendMessageBuilder,
62    ) -> Result<Message, E> {
63        Ok(builder.build().await?)
64    }
65
66    #[allow(unused_variables)]
67    async fn after_help_command(&self, context: Context<E, S>, message: Message) -> Result<(), E> {
68        Ok(())
69    }
70
71    async fn get_channel(&self, context: Context<E, S>) -> Result<String, E> {
72        Ok(context.message.channel.clone())
73    }
74
75    async fn no_command_found(
76        &self,
77        context: Context<E, S>,
78        name: String,
79        builder: &mut SendMessageBuilder,
80    ) -> Result<(), E>;
81}
82
83#[derive(Debug)]
84pub struct DefaultHelpCommand;
85
86#[async_trait]
87impl<
88    E: From<Error> + Clone + Debug + Send + Sync + 'static,
89    S: Debug + Clone + Send + Sync + 'static,
90> HelpCommand<E, S> for DefaultHelpCommand
91{
92    async fn create_global_help(
93        &self,
94        _context: Context<E, S>,
95        commands: Vec<Command<E, S>>,
96        builder: &mut SendMessageBuilder,
97    ) -> Result<(), E> {
98        let mut lines = vec!["```".to_string()];
99
100        for command in commands {
101            lines.push(format!(
102                "{} - {}",
103                &command.name,
104                command
105                    .description
106                    .as_ref()
107                    .map(|desc| desc.split('\n').next().unwrap())
108                    .unwrap_or("No description")
109            ));
110        }
111
112        lines.push("```".to_string());
113
114        builder.content(lines.join("\n"));
115
116        Ok(())
117    }
118
119    async fn create_command_help(
120        &self,
121        context: Context<E, S>,
122        command: Command<E, S>,
123        builder: &mut SendMessageBuilder,
124    ) -> Result<(), E> {
125        let mut lines = vec!["```".to_string(), format!("{}:", &command.name)];
126
127        let mut usage = command.parents.clone();
128        usage.push(command.name.clone());
129        usage.push(command.signature.clone().unwrap_or_default());
130
131        lines.push(format!(
132            "    Usage: {}{}",
133            context.clean_prefix(),
134            usage.join(" ")
135        ));
136
137        if !command.aliases.is_empty() {
138            lines.push(format!("    Aliases: {}", command.aliases.join(", ")));
139        }
140
141        if let Some(description) = command.description.clone() {
142            lines.push("".to_string());
143            lines.push(description);
144        }
145
146        lines.push("```".to_string());
147
148        builder.content(lines.join("\n"));
149
150        Ok(())
151    }
152
153    async fn create_group_help(
154        &self,
155        context: Context<E, S>,
156        command: Command<E, S>,
157        builder: &mut SendMessageBuilder,
158    ) -> Result<(), E> {
159        let mut lines = vec!["```".to_string(), format!("{}:", &command.name)];
160
161        let mut usage = command.parents.clone();
162        usage.push(command.name.clone());
163        usage.push(command.signature.clone().unwrap_or_default());
164
165        lines.push(format!(
166            "    Usage: {}{}",
167            context.clean_prefix(),
168            usage.join(" ")
169        ));
170
171        if !command.aliases.is_empty() {
172            lines.push(format!("    Aliases: {}", command.aliases.join(", ")));
173        }
174
175        if let Some(description) = command.description.clone() {
176            lines.push("".to_string());
177            lines.push(description);
178            lines.push("".to_string());
179        }
180
181        let children = self
182            .filter_commands(context.clone(), command.children())
183            .await?;
184
185        if !children.is_empty() {
186            lines.push("Commands:".to_string());
187        };
188
189        for command in children {
190            lines.push(format!(
191                "    {} - {}",
192                &command.name,
193                command
194                    .description
195                    .as_ref()
196                    .map(|desc| desc.split('\n').next().unwrap())
197                    .unwrap_or("No description")
198            ));
199        }
200
201        lines.push("```".to_string());
202
203        builder.content(lines.join("\n"));
204
205        Ok(())
206    }
207
208    async fn no_command_found(
209        &self,
210        _context: Context<E, S>,
211        name: String,
212        builder: &mut SendMessageBuilder,
213    ) -> Result<(), E> {
214        builder.content(format!("Command `{name}` not found."));
215
216        Ok(())
217    }
218}
219
220#[derive(Clone)]
221struct HelpCommandImpl;
222
223#[async_trait]
224impl<
225    E: From<Error> + Clone + Debug + Send + Sync + 'static,
226    S: Debug + Clone + Send + Sync + 'static,
227> CommandHandle<(), E, S> for HelpCommandImpl
228{
229    async fn handle(&self, context: Context<E, S>) -> Result<(), E> {
230        let args = Vec::<String>::from_context(&context).await?;
231
232        let channel_id = context.help_command.get_channel(context.clone()).await?;
233        let mut builder = SendMessageBuilder::new(context.http.clone(), channel_id);
234
235        let commands = context
236            .help_command
237            .filter_commands(context.clone(), context.commands.get_commands())
238            .await?;
239
240        if args.is_empty() {
241            context
242                .help_command
243                .create_global_help(context.clone(), commands, &mut builder)
244                .await?;
245        } else {
246            if let Some(command) = context.commands.get_command_from_slice(&args) {
247                if command.children.is_empty() {
248                    context
249                        .help_command
250                        .create_command_help(context.clone(), command, &mut builder)
251                        .await?;
252                } else {
253                    context
254                        .help_command
255                        .create_group_help(context.clone(), command, &mut builder)
256                        .await?;
257                }
258            } else {
259                context
260                    .help_command
261                    .no_command_found(context.clone(), args.join(" "), &mut builder)
262                    .await?;
263            }
264        }
265
266        let message = context
267            .help_command
268            .send_help_command(context.clone(), builder)
269            .await?;
270        context
271            .help_command
272            .after_help_command(context.clone(), message)
273            .await?;
274
275        Ok(())
276    }
277}
278
279pub(crate) fn help_command<
280    E: From<Error> + Clone + Debug + Send + Sync + 'static,
281    S: Debug + Clone + Send + Sync + 'static,
282>() -> Command<E, S> {
283    Command::new("help", HelpCommandImpl)
284        .signature("<command>")
285        .description("Shows help for a command, group or all commands")
286}