diff --git a/bot/modules/meta/help.py b/bot/modules/meta/help.py new file mode 100644 index 00000000..3e77ea1c --- /dev/null +++ b/bot/modules/meta/help.py @@ -0,0 +1,188 @@ +import discord + +from utils.lib import prop_tabulate +from utils import interactive, ctx_addons # noqa +from wards import is_guild_admin + +from .module import module + + +# Set the command groups to appear in the help +group_hints = { + 'Productivity': "*Various productivity tools.*", + 'Statistics': "*StudyLion leaderboards and study statistics.*", + 'Personal Settings': "*Tell me about yourself!*", + 'Guild Configuration': "*Control how I behave in your server.*", + 'Meta': "*Information about me!*" +} + +standard_group_order = ( + ('Productivity', 'Statistics', 'Personal Settings', 'Meta') +) + +mod_group_order = ( + ('Moderation', 'Meta'), + ('Productivity', 'Statistics', 'Personal Settings') +) + +admin_group_order = ( + ('Guild Configuration', 'Moderation', 'Meta'), + ('Productivity', 'Statistics', 'Personal Settings') +) + +# Help embed format +# TODO: Add config fields for this +title = "LionBot Command List" +header = """ +For more detailed information about each command use `{ctx.best_prefix}help `. +""" + + +@module.cmd("help", + group="Meta", + desc="LionBot command list.") +async def cmd_help(ctx): + """ + Usage``: + {prefix}help [cmdname] + Description: + When used with no arguments, displays a list of commands with brief descriptions. + Otherwise, shows documentation for the provided command. + Examples: + {prefix}help + {prefix}help top + {prefix}help timezone + """ + if ctx.arg_str: + # Attempt to fetch the command + command = ctx.client.cmd_names.get(ctx.arg_str.strip(), None) + if command is None: + return await ctx.error_reply( + ("Command `{}` not found!\n" + "Write `{}help` to see a list of commands.").format(ctx.args, ctx.best_prefix) + ) + + smart_help = getattr(command, 'smart_help', None) + if smart_help is not None: + return await smart_help(ctx) + + help_fields = command.long_help.copy() + help_map = {field_name: i for i, (field_name, _) in enumerate(help_fields)} + + if not help_map: + await ctx.reply("No documentation has been written for this command yet!") + + for name, pos in help_map.items(): + if name.endswith("``"): + # Handle codeline help fields + help_fields[pos] = ( + name.strip("`"), + "`{}`".format('`\n`'.join(help_fields[pos][1].splitlines())) + ) + elif name.endswith(":"): + # Handle property/value help fields + lines = help_fields[pos][1].splitlines() + + names = [] + values = [] + for line in lines: + split = line.split(":", 1) + names.append(split[0] if len(split) > 1 else "") + values.append(split[-1]) + + help_fields[pos] = ( + name.strip(':'), + prop_tabulate(names, values) + ) + elif name == "Related": + # Handle the related field + names = [cmd_name.strip() for cmd_name in help_fields[pos][1].split(',')] + names.sort(key=len) + values = [ + (getattr(ctx.client.cmd_names.get(cmd_name, None), 'desc', '') or '').format(ctx=ctx) + for cmd_name in names + ] + help_fields[pos] = ( + name, + prop_tabulate(names, values) + ) + + aliases = getattr(command, 'aliases', []) + alias_str = "(Aliases `{}`.)".format("`, `".join(aliases)) if aliases else "" + + # Build the embed + embed = discord.Embed( + title="`{}` command documentation. {}".format(command.name, alias_str), + colour=discord.Colour(0x9b59b6) + ) + for fieldname, fieldvalue in help_fields: + embed.add_field( + name=fieldname, + value=fieldvalue.format(ctx=ctx, prefix=ctx.best_prefix), + inline=False + ) + + embed.set_footer( + text="[optional] and denote optional and required arguments, respectively." + ) + + # Post the embed + await ctx.reply(embed=embed) + else: + # Build the command groups + cmd_groups = {} + for command in ctx.client.cmds: + # Get the command group + group = getattr(command, 'group', "Misc") + cmd_group = cmd_groups.get(group, []) + if not cmd_group: + cmd_groups[group] = cmd_group + + # Add the command name and description to the group + cmd_group.append((command.name, getattr(command, 'desc', ''))) + + # Add any required aliases + for alias, desc in getattr(command, 'help_aliases', {}).items(): + cmd_group.append((alias, desc)) + + # Turn the command groups into strings + stringy_cmd_groups = {} + for group_name, cmd_group in cmd_groups.items(): + cmd_group.sort(key=lambda tup: len(tup[0])) + stringy_cmd_groups[group_name] = prop_tabulate(*zip(*cmd_group)) + + # Now put everything into a bunch of embeds + if ctx.guild and is_guild_admin(ctx.author): + group_order = admin_group_order + else: + group_order = standard_group_order + + help_embeds = [] + for page_groups in group_order: + embed = discord.Embed( + description=header.format(ctx=ctx), + colour=discord.Colour(0x9b59b6), + title=title + ) + for group in page_groups: + group_hint = group_hints.get(group, '').format(ctx=ctx) + group_str = stringy_cmd_groups.get(group, None) + if group_str: + embed.add_field( + name=group, + value="{}\n{}".format(group_hint, group_str).format(ctx=ctx), + inline=False + ) + help_embeds.append(embed) + + # Add the page numbers + for i, embed in enumerate(help_embeds): + embed.set_footer(text="Page {}/{}".format(i+1, len(help_embeds))) + + # Send the embeds + if help_embeds: + await ctx.pager(help_embeds) + else: + await ctx.reply( + embed=discord.Embed(description=header, colour=discord.Colour(0x9b59b6)) + ) diff --git a/bot/modules/meta/module.py b/bot/modules/meta/module.py new file mode 100644 index 00000000..1e030669 --- /dev/null +++ b/bot/modules/meta/module.py @@ -0,0 +1,3 @@ +from LionModule import LionModule + +module = LionModule("Meta")