diff --git a/services/grid-bot/lib/commands/Modules/Commands/Help.cs b/services/grid-bot/lib/commands/Modules/Commands/Help.cs new file mode 100755 index 00000000..8de90ade --- /dev/null +++ b/services/grid-bot/lib/commands/Modules/Commands/Help.cs @@ -0,0 +1,259 @@ +namespace Grid.Bot.Commands.Public; + +using System; +using System.Linq; +using System.Reflection; +using System.Diagnostics; +using System.Threading.Tasks; +using System.Collections.Generic; + +using Discord; + +using Discord.Commands; + +using Text.Extensions; + +using Utility; +using Extensions; + + + + +/// +/// Interaction handler for the support commands. +/// +public class Help : ModuleBase +{ + private readonly IAdminUtility _adminUtility; + private readonly CommandsSettings _commandsSettings; + + private readonly HashSet<(string[] aliases, Embed embed, BotRole role)> _aliasesToEmbeds = new(); + + /// + /// Construct a new instance of . + /// + /// The . + /// The . + /// The . + /// + /// - cannot be null. + /// - cannot be null. + /// - cannot be null. + /// + public Help( + IAdminUtility adminUtility, + CommandService commandService, + CommandsSettings commandsSettings + ) + { + ArgumentNullException.ThrowIfNull(commandService, nameof(commandService)); + _adminUtility = adminUtility ?? throw new ArgumentNullException(nameof(adminUtility)); + _commandsSettings = commandsSettings ?? throw new ArgumentNullException(nameof(commandsSettings)); + + SetupCache(commandService, commandsSettings); + } + + + private void SetupCache(CommandService commandService, CommandsSettings commandsSettings) + { + foreach (var module in commandService.Modules) + { + bool isGrouped = !string.IsNullOrEmpty(module.Group); + var aliases = new HashSet(); + + if (isGrouped) + { + aliases.Add(module.Group); + aliases.UnionWith(module.Aliases); + } + else + { + aliases.Add(module.Commands[0].Name); + aliases.UnionWith(module.Commands[0].Aliases); + } + + var botRoleAttribute = module.Attributes.FirstOrDefault(attrib => attrib.GetType() == typeof(RequireBotRoleAttribute)) as RequireBotRoleAttribute; + var requiredPermission = botRoleAttribute?.BotRole ?? BotRole.Default; + + var builder = new EmbedBuilder().WithColor(Color.Green).WithTimestamp(DateTimeOffset.Now); + + if (isGrouped) + { + var title = module.Group; + + if (module.Aliases.Skip(1).Count() > 1) + title += string.Format(" - {0}", module.Aliases.Skip(1).Join(", ")); + + builder.WithTitle(title); + + if (!string.IsNullOrEmpty(module.Summary)) + builder.WithDescription(module.Summary); + + foreach (var command in module.Commands) + builder.AddField(field => + { + var fieldName = command.Name; + + var commandAliases = command.Aliases.Select(alias => alias.Split(' ').ElementAt(1)).Skip(1).Distinct(); + if (commandAliases.Count() > 1) + fieldName += string.Format(" - {0}", commandAliases.Join(", ")); + + field.WithName(fieldName); + + var fieldValue = ""; + + if (command.Parameters.Count > 0) + fieldValue += string.Format( + "Command Arguments:\n{0}{1} {2} ", + commandsSettings.Prefix, + module.Group, + command.Name + ); + + foreach (var parameter in command.Parameters) + { + if (parameter.IsOptional) + fieldValue += string.Format("<*{0}*>", parameter.Name); + else + fieldValue += string.Format("<***{0}***>", parameter.Name); + + if (parameter.IsOptional) + if (parameter.DefaultValue is null || (parameter.DefaultValue is string str && string.IsNullOrEmpty(str))) + fieldValue += "?"; + else + fieldValue += string.Format("[=*{0}*]", parameter.DefaultValue); + + if (parameter.IsRemainder) + fieldValue += "... "; + else + fieldValue += " "; + } + + if (!string.IsNullOrEmpty(command.Summary)) + fieldValue += string.Format("\n\n{0}", command.Summary); + + field.WithValue(fieldValue); + }); + } + else + { + var command = module.Commands[0]; + + var title = command.Name; + + if (command.Aliases.Skip(1).Count() > 1) + title += string.Format(" - {0}", command.Aliases.Skip(1).Join(", ")); + + builder.WithTitle(title); + + var description = ""; + + if (command.Parameters.Count > 0) + description += string.Format( + "Command Arguments:\n{0}{1} ", + commandsSettings.Prefix, + command.Name + ); + + foreach (var parameter in command.Parameters) + { + if (parameter.IsOptional) + description += string.Format("<*{0}*>", parameter.Name); + else + description += string.Format("<***{0}***>", parameter.Name); + + if (parameter.IsOptional) + if (parameter.DefaultValue is null || (parameter.DefaultValue is string str && string.IsNullOrEmpty(str))) + description += "?"; + else + description += string.Format("[=*{0}*]", parameter.DefaultValue); + + if (parameter.IsRemainder) + description += "... "; + else + description += " "; + } + + if (!string.IsNullOrEmpty(command.Summary)) + description += string.Format("\n\n{0}", command.Summary); + + builder.WithDescription(description); + } + + _aliasesToEmbeds.Add((aliases.ToArray(), builder.Build(), requiredPermission)); + } + } + + /// + /// Gets brief help for all commands or a specific command. + /// + /// The optional command name + [Command("help"), Summary("Gets help for commands.")] + public async Task GetHelp( + [Summary("The optional command name.")] + string commandName = "" + ) + { + if (!string.IsNullOrEmpty(commandName)) + { + // Grouped modules (such as client settings) will have their aliases and group set. + // If they aren't then we assume it has only one command. + + var (embed, role) = _aliasesToEmbeds + .Where(tup => tup.aliases.Contains(commandName.ToLowerInvariant())) + .Select(tup => (tup.embed, tup.role)) + .FirstOrDefault(); + + if (embed == null) + { + await this.ReplyWithReferenceAsync($"Unable to find command with name '{commandName}'!"); + + return; + } + + if (!_adminUtility.IsInRole(Context.User, role)) return; + + await this.ReplyWithReferenceAsync(embed: embed); + + return; + } + + var currentEmbed = new EmbedBuilder() + .WithTitle("Commands") + .WithDescription($"Use the {_commandsSettings.Prefix}help to see more information on commands marked as **grouped**.") + .WithColor(Color.Green) + .WithTimestamp(DateTimeOffset.Now); + + var embeds = new List(); + + for (int i = 1; i <= _aliasesToEmbeds.Count; i++) + { + var (aliases, embed, role) = _aliasesToEmbeds.ElementAt(i - 1); + if (!_adminUtility.IsInRole(Context.User, role)) continue; + + var fieldName = aliases.First(); + + if (embed.Fields.Length > 0) fieldName += " - **grouped**"; + + currentEmbed.AddField(fieldName, embed.Description); + + // For every 24 fields, add to the list and reset current embed. + if (i % EmbedBuilder.MaxFieldCount == 0) + { + embeds.Add(currentEmbed); + currentEmbed = new EmbedBuilder() + .WithColor(Color.Green) + .WithTimestamp(DateTimeOffset.Now); + } + } + + if (embeds.Count == 0) embeds.Add(currentEmbed); + + embeds.Last().WithAuthor(Context.User); + + await this.ReplyWithReferenceAsync("Help for the commands:", embed: embeds.First().Build()); + + embeds = embeds.Skip(1).ToList(); + foreach (var embed in embeds) await this.ReplyWithReferenceAsync(embed: embed.Build()); + } +}