Improve help command (#116)
Some checks failed
Deploy MkDocs / deploy (push) Has been cancelled

This commit is contained in:
Slipstream 2025-06-15 20:39:20 -06:00 committed by GitHub
parent 17751d3b09
commit cec747a575
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 101 additions and 17 deletions

View File

@ -1,6 +1,8 @@
from collections import defaultdict
from typing import List, Optional from typing import List, Optional
from .core import Command, CommandContext, CommandHandler from ...utils import Paginator
from .core import Command, CommandContext, CommandHandler, Group
class HelpCommand(Command): class HelpCommand(Command):
@ -15,17 +17,12 @@ class HelpCommand(Command):
if not cmd or cmd.name.lower() != command.lower(): if not cmd or cmd.name.lower() != command.lower():
await ctx.send(f"Command '{command}' not found.") await ctx.send(f"Command '{command}' not found.")
return return
description = cmd.description or cmd.brief or "No description provided." if isinstance(cmd, Group):
await ctx.send(f"**{ctx.prefix}{cmd.name}**\n{description}") await self.send_group_help(ctx, cmd)
else:
lines: List[str] = []
for registered in dict.fromkeys(handler.commands.values()):
brief = registered.brief or registered.description or ""
lines.append(f"{ctx.prefix}{registered.name} - {brief}".strip())
if lines:
await ctx.send("\n".join(lines))
else: else:
await ctx.send("No commands available.") await self.send_command_help(ctx, cmd)
else:
await self.send_bot_help(ctx)
super().__init__( super().__init__(
callback, callback,
@ -33,3 +30,42 @@ class HelpCommand(Command):
brief="Show command help.", brief="Show command help.",
description="Displays help for commands.", description="Displays help for commands.",
) )
async def send_bot_help(self, ctx: CommandContext) -> None:
groups = defaultdict(list)
for cmd in dict.fromkeys(self.handler.commands.values()):
key = cmd.cog.cog_name if cmd.cog else "No Category"
groups[key].append(cmd)
paginator = Paginator()
for cog_name, cmds in groups.items():
paginator.add_line(f"**{cog_name}**")
for cmd in cmds:
brief = cmd.brief or cmd.description or ""
paginator.add_line(f"{ctx.prefix}{cmd.name} - {brief}".strip())
paginator.add_line("")
pages = paginator.pages
if not pages:
await ctx.send("No commands available.")
return
for page in pages:
await ctx.send(page)
async def send_command_help(self, ctx: CommandContext, command: Command) -> None:
description = command.description or command.brief or "No description provided."
await ctx.send(f"**{ctx.prefix}{command.name}**\n{description}")
async def send_group_help(self, ctx: CommandContext, group: Group) -> None:
paginator = Paginator()
description = group.description or group.brief or "No description provided."
paginator.add_line(f"**{ctx.prefix}{group.name}**\n{description}")
if group.commands:
for sub in dict.fromkeys(group.commands.values()):
brief = sub.brief or sub.description or ""
paginator.add_line(
f"{ctx.prefix}{group.name} {sub.name} - {brief}".strip()
)
for page in paginator.pages:
await ctx.send(page)

View File

@ -11,7 +11,11 @@ The command handler registers a `help` command automatically. Use it to list all
!help ping # shows help for the "ping" command !help ping # shows help for the "ping" command
``` ```
The help command will show each command's brief description if provided. Commands are grouped by their Cog name and paginated so that long help
lists are split into multiple messages using the `Paginator` utility.
If you need custom formatting you can subclass
`HelpCommand` and override `send_command_help` or `send_group_help`.
## Checks ## Checks

View File

@ -1,6 +1,7 @@
import pytest import pytest
from disagreement.ext.commands.core import CommandHandler, Command from disagreement.ext import commands
from disagreement.ext.commands.core import CommandHandler, Command, Group
from disagreement.models import Message from disagreement.models import Message
@ -13,15 +14,21 @@ class DummyBot:
return {"id": "1", "channel_id": channel_id, "content": content} return {"id": "1", "channel_id": channel_id, "content": content}
class MyCog(commands.Cog):
def __init__(self, client) -> None:
super().__init__(client)
@commands.command()
async def foo(self, ctx: commands.CommandContext) -> None:
pass
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_help_lists_commands(): async def test_help_lists_commands():
bot = DummyBot() bot = DummyBot()
handler = CommandHandler(client=bot, prefix="!") handler = CommandHandler(client=bot, prefix="!")
async def foo(ctx): handler.add_cog(MyCog(bot))
pass
handler.add_command(Command(foo, name="foo", brief="Foo cmd"))
msg_data = { msg_data = {
"id": "1", "id": "1",
@ -33,6 +40,7 @@ async def test_help_lists_commands():
msg = Message(msg_data, client_instance=bot) msg = Message(msg_data, client_instance=bot)
await handler.process_commands(msg) await handler.process_commands(msg)
assert any("foo" in m for m in bot.sent) assert any("foo" in m for m in bot.sent)
assert any("MyCog" in m for m in bot.sent)
@pytest.mark.asyncio @pytest.mark.asyncio
@ -55,3 +63,39 @@ async def test_help_specific_command():
msg = Message(msg_data, client_instance=bot) msg = Message(msg_data, client_instance=bot)
await handler.process_commands(msg) await handler.process_commands(msg)
assert any("Bar desc" in m for m in bot.sent) assert any("Bar desc" in m for m in bot.sent)
from disagreement.ext.commands.help import HelpCommand
class CustomHelp(HelpCommand):
async def send_command_help(self, ctx, command):
await ctx.send(f"custom {command.name}")
async def send_group_help(self, ctx, group):
await ctx.send(f"group {group.name}")
@pytest.mark.asyncio
async def test_custom_help_methods():
bot = DummyBot()
handler = CommandHandler(client=bot, prefix="!")
handler.remove_command("help")
handler.add_command(CustomHelp(handler))
async def sub(ctx):
pass
group = Group(sub, name="grp")
handler.add_command(group)
msg_data = {
"id": "1",
"channel_id": "c",
"author": {"id": "2", "username": "u", "discriminator": "0001"},
"content": "!help grp",
"timestamp": "t",
}
msg = Message(msg_data, client_instance=bot)
await handler.process_commands(msg)
assert any("group grp" in m for m in bot.sent)