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}