Skip to content

Commit ab65c9a

Browse files
committed
#336: Help Command
Add the help command for text based commands. This will cache the embeds for each indivual command on the first usage of the command.
1 parent aa14bcf commit ab65c9a

File tree

1 file changed

+259
-0
lines changed
  • services/grid-bot/lib/commands/Modules/Commands

1 file changed

+259
-0
lines changed
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
namespace Grid.Bot.Commands.Public;
2+
3+
using System;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Diagnostics;
7+
using System.Threading.Tasks;
8+
using System.Collections.Generic;
9+
10+
using Discord;
11+
12+
using Discord.Commands;
13+
14+
using Text.Extensions;
15+
16+
using Utility;
17+
using Extensions;
18+
19+
20+
21+
22+
/// <summary>
23+
/// Interaction handler for the support commands.
24+
/// </summary>
25+
public class Help : ModuleBase
26+
{
27+
private readonly IAdminUtility _adminUtility;
28+
private readonly CommandsSettings _commandsSettings;
29+
30+
private readonly HashSet<(string[] aliases, Embed embed, BotRole role)> _aliasesToEmbeds = new();
31+
32+
/// <summary>
33+
/// Construct a new instance of <see cref="Support"/>.
34+
/// </summary>
35+
/// <param name="adminUtility">The <see cref="IAdminUtility"/>.</param>
36+
/// <param name="commandService">The <see cref="CommandService"/>.</param>
37+
/// <param name="commandsSettings">The <see cref="CommandsSettings"/>.</param>
38+
/// <exception cref="ArgumentNullException">
39+
/// - <paramref name="adminUtility"/> cannot be null.
40+
/// - <paramref name="commandService"/> cannot be null.
41+
/// - <paramref name="commandsSettings"/> cannot be null.
42+
/// </exception>
43+
public Help(
44+
IAdminUtility adminUtility,
45+
CommandService commandService,
46+
CommandsSettings commandsSettings
47+
)
48+
{
49+
ArgumentNullException.ThrowIfNull(commandService, nameof(commandService));
50+
_adminUtility = adminUtility ?? throw new ArgumentNullException(nameof(adminUtility));
51+
_commandsSettings = commandsSettings ?? throw new ArgumentNullException(nameof(commandsSettings));
52+
53+
SetupCache(commandService, commandsSettings);
54+
}
55+
56+
57+
private void SetupCache(CommandService commandService, CommandsSettings commandsSettings)
58+
{
59+
foreach (var module in commandService.Modules)
60+
{
61+
bool isGrouped = !string.IsNullOrEmpty(module.Group);
62+
var aliases = new HashSet<string>();
63+
64+
if (isGrouped)
65+
{
66+
aliases.Add(module.Group);
67+
aliases.UnionWith(module.Aliases);
68+
}
69+
else
70+
{
71+
aliases.Add(module.Commands[0].Name);
72+
aliases.UnionWith(module.Commands[0].Aliases);
73+
}
74+
75+
var botRoleAttribute = module.Attributes.FirstOrDefault(attrib => attrib.GetType() == typeof(RequireBotRoleAttribute)) as RequireBotRoleAttribute;
76+
var requiredPermission = botRoleAttribute?.BotRole ?? BotRole.Default;
77+
78+
var builder = new EmbedBuilder().WithColor(Color.Green).WithTimestamp(DateTimeOffset.Now);
79+
80+
if (isGrouped)
81+
{
82+
var title = module.Group;
83+
84+
if (module.Aliases.Skip(1).Count() > 1)
85+
title += string.Format(" - {0}", module.Aliases.Skip(1).Join(", "));
86+
87+
builder.WithTitle(title);
88+
89+
if (!string.IsNullOrEmpty(module.Summary))
90+
builder.WithDescription(module.Summary);
91+
92+
foreach (var command in module.Commands)
93+
builder.AddField(field =>
94+
{
95+
var fieldName = command.Name;
96+
97+
var commandAliases = command.Aliases.Select(alias => alias.Split(' ').ElementAt(1)).Skip(1).Distinct();
98+
if (commandAliases.Count() > 1)
99+
fieldName += string.Format(" - {0}", commandAliases.Join(", "));
100+
101+
field.WithName(fieldName);
102+
103+
var fieldValue = "";
104+
105+
if (command.Parameters.Count > 0)
106+
fieldValue += string.Format(
107+
"Command Arguments:\n{0}{1} {2} ",
108+
commandsSettings.Prefix,
109+
module.Group,
110+
command.Name
111+
);
112+
113+
foreach (var parameter in command.Parameters)
114+
{
115+
if (parameter.IsOptional)
116+
fieldValue += string.Format("<*{0}*>", parameter.Name);
117+
else
118+
fieldValue += string.Format("<***{0}***>", parameter.Name);
119+
120+
if (parameter.IsOptional)
121+
if (parameter.DefaultValue is null || (parameter.DefaultValue is string str && string.IsNullOrEmpty(str)))
122+
fieldValue += "?";
123+
else
124+
fieldValue += string.Format("[=*{0}*]", parameter.DefaultValue);
125+
126+
if (parameter.IsRemainder)
127+
fieldValue += "... ";
128+
else
129+
fieldValue += " ";
130+
}
131+
132+
if (!string.IsNullOrEmpty(command.Summary))
133+
fieldValue += string.Format("\n\n{0}", command.Summary);
134+
135+
field.WithValue(fieldValue);
136+
});
137+
}
138+
else
139+
{
140+
var command = module.Commands[0];
141+
142+
var title = command.Name;
143+
144+
if (command.Aliases.Skip(1).Count() > 1)
145+
title += string.Format(" - {0}", command.Aliases.Skip(1).Join(", "));
146+
147+
builder.WithTitle(title);
148+
149+
var description = "";
150+
151+
if (command.Parameters.Count > 0)
152+
description += string.Format(
153+
"Command Arguments:\n{0}{1} ",
154+
commandsSettings.Prefix,
155+
command.Name
156+
);
157+
158+
foreach (var parameter in command.Parameters)
159+
{
160+
if (parameter.IsOptional)
161+
description += string.Format("<*{0}*>", parameter.Name);
162+
else
163+
description += string.Format("<***{0}***>", parameter.Name);
164+
165+
if (parameter.IsOptional)
166+
if (parameter.DefaultValue is null || (parameter.DefaultValue is string str && string.IsNullOrEmpty(str)))
167+
description += "?";
168+
else
169+
description += string.Format("[=*{0}*]", parameter.DefaultValue);
170+
171+
if (parameter.IsRemainder)
172+
description += "... ";
173+
else
174+
description += " ";
175+
}
176+
177+
if (!string.IsNullOrEmpty(command.Summary))
178+
description += string.Format("\n\n{0}", command.Summary);
179+
180+
builder.WithDescription(description);
181+
}
182+
183+
_aliasesToEmbeds.Add((aliases.ToArray(), builder.Build(), requiredPermission));
184+
}
185+
}
186+
187+
/// <summary>
188+
/// Gets brief help for all commands or a specific command.
189+
/// </summary>
190+
/// <param name="commandName">The optional command name</param>
191+
[Command("help"), Summary("Gets help for commands.")]
192+
public async Task GetHelp(
193+
[Summary("The optional command name.")]
194+
string commandName = ""
195+
)
196+
{
197+
if (!string.IsNullOrEmpty(commandName))
198+
{
199+
// Grouped modules (such as client settings) will have their aliases and group set.
200+
// If they aren't then we assume it has only one command.
201+
202+
var (embed, role) = _aliasesToEmbeds
203+
.Where(tup => tup.aliases.Contains(commandName.ToLowerInvariant()))
204+
.Select(tup => (tup.embed, tup.role))
205+
.FirstOrDefault();
206+
207+
if (embed == null)
208+
{
209+
await this.ReplyWithReferenceAsync($"Unable to find command with name '{commandName}'!");
210+
211+
return;
212+
}
213+
214+
if (!_adminUtility.IsInRole(Context.User, role)) return;
215+
216+
await this.ReplyWithReferenceAsync(embed: embed);
217+
218+
return;
219+
}
220+
221+
var currentEmbed = new EmbedBuilder()
222+
.WithTitle("Commands")
223+
.WithDescription($"Use the {_commandsSettings.Prefix}help <commandName> to see more information on commands marked as **grouped**.")
224+
.WithColor(Color.Green)
225+
.WithTimestamp(DateTimeOffset.Now);
226+
227+
var embeds = new List<EmbedBuilder>();
228+
229+
for (int i = 1; i <= _aliasesToEmbeds.Count; i++)
230+
{
231+
var (aliases, embed, role) = _aliasesToEmbeds.ElementAt(i - 1);
232+
if (!_adminUtility.IsInRole(Context.User, role)) continue;
233+
234+
var fieldName = aliases.First();
235+
236+
if (embed.Fields.Length > 0) fieldName += " - **grouped**";
237+
238+
currentEmbed.AddField(fieldName, embed.Description);
239+
240+
// For every 24 fields, add to the list and reset current embed.
241+
if (i % EmbedBuilder.MaxFieldCount == 0)
242+
{
243+
embeds.Add(currentEmbed);
244+
currentEmbed = new EmbedBuilder()
245+
.WithColor(Color.Green)
246+
.WithTimestamp(DateTimeOffset.Now);
247+
}
248+
}
249+
250+
if (embeds.Count == 0) embeds.Add(currentEmbed);
251+
252+
embeds.Last().WithAuthor(Context.User);
253+
254+
await this.ReplyWithReferenceAsync("Help for the commands:", embed: embeds.First().Build());
255+
256+
embeds = embeds.Skip(1).ToList();
257+
foreach (var embed in embeds) await this.ReplyWithReferenceAsync(embed: embed.Build());
258+
}
259+
}

0 commit comments

Comments
 (0)