Skip to content

Commit 924225b

Browse files
committed
Add /help command and ephemeral locale system
Introduces an interactive Component v2 help browser (/help) with category navigation, access filtering, and per-command detail pages. Refactors cog.py → commands.py replacing LazyHybridCommand with CommandBuilder/GroupBuilder and adding ParamInfo/help text. Adds an ephemeral locale system (ephemeral_scope, locale_for) so ctx.t() and send_message() resolve locale automatically. Decouples BaseLayoutView and BaseModal from Context. Promotes check_user_access to Bot with overloads for pre-computed args. Signed-off-by: Taku <45324516+Taaku18@users.noreply.github.com>
1 parent 8aed0b7 commit 924225b

25 files changed

Lines changed: 1959 additions & 650 deletions

config.yaml.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ bot:
7070
# Must install the jishaku package with "pdm install -G debug" to enable.
7171
# enable_jishaku: false
7272

73+
# Whether to hide owner-only commands from users who are not the bot owner in /help.
74+
# hide_owner_commands: true
75+
76+
# Whether to hide commands the user cannot access from the /help menu.
77+
# Cogs with no accessible commands are also omitted.
78+
# hide_inaccessible: true
79+
7380
# Required: The URL to the bot's logviewer address.
7481
log_url: "https://example.com/"
7582

modmail/cogs/modmail/__init__.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99

1010
from typing import TYPE_CHECKING
1111

12-
from modmail.core import Bot, Cog, create_cog
12+
import discord
13+
14+
from modmail.core import Bot, Cog, _, create_cog
1315

1416
from .commands import all_commands
1517
from .listeners import all_listeners
@@ -20,7 +22,14 @@ class Modmail(Cog): # Makes linters happy
2022
"""Core Modmail commands cog for the Modmail bot."""
2123

2224
else:
23-
Modmail = create_cog("Modmail", all_commands=all_commands, other_methods=all_listeners)
25+
Modmail = create_cog(
26+
"Modmail",
27+
all_commands=all_commands,
28+
other_methods=all_listeners,
29+
help_name=_("ftl-view-help-category-modmail-name"),
30+
help_description=_("ftl-view-help-category-modmail-description"),
31+
help_color=discord.Color.blurple(),
32+
)
2433

2534

2635
__all__ = ["Modmail", "setup"]

modmail/cogs/modmail/commands/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
from .setup import setup_command
1616

1717
if TYPE_CHECKING:
18-
from modmail.core import LazyHybridCommand
18+
from modmail.core import CommandBuilder
1919

2020
__all__ = [
2121
"all_commands",
2222
]
2323

24-
all_commands: list[LazyHybridCommand[Any]] = [
24+
all_commands: list[CommandBuilder[Any]] = [
2525
setup_command,
2626
reply_command,
2727
close_command,

modmail/cogs/modmail/commands/close.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import discord
1414

15-
from modmail.core import Context, _, in_modmail_ticket, lazy_hybrid_command, staff_only, wrap
15+
from modmail.core import Context, ParamInfo, _, bot_command, in_modmail_ticket, staff_only
1616
from modmail.enum import TicketMessageType, TicketStatus
1717

1818
if TYPE_CHECKING:
@@ -24,19 +24,20 @@
2424

2525

2626
@staff_only
27-
@wrap(
28-
discord.app_commands.rename,
29-
attachment=_("ftl-cmd-close-param-attachment-name"),
30-
message=_("ftl-cmd-close-param-message-name"),
31-
)
32-
@wrap(
33-
discord.app_commands.describe,
34-
attachment=_("ftl-cmd-close-param-attachment-description"),
35-
message=_("ftl-cmd-close-param-message-description"),
36-
)
37-
@lazy_hybrid_command(
27+
@bot_command(
3828
name=_("ftl-cmd-close-name"),
3929
description=_("ftl-cmd-close-description"),
30+
help=_("ftl-cmd-close-help"),
31+
param_info={
32+
"attachment": ParamInfo(
33+
name=_("ftl-cmd-close-param-attachment-name"),
34+
description=_("ftl-cmd-close-param-attachment-description"),
35+
),
36+
"message": ParamInfo(
37+
name=_("ftl-cmd-close-param-message-name"),
38+
description=_("ftl-cmd-close-param-message-description"),
39+
),
40+
},
4041
)
4142
@in_modmail_ticket()
4243
async def close_command(
@@ -46,16 +47,16 @@ async def close_command(
4647
*,
4748
message: str = "",
4849
) -> None:
49-
"""Close a ticket in Modmail.
50+
"""Close the current ticket and send a close message to the recipient(s).
5051
5152
Args:
5253
cog: The Modmail cog instance.
53-
ctx: The command context containing information about the invocation.
54-
attachment: An optional attachment to include in the close message. Auto parsed by discord.py.
55-
message: The message to send as a close message.
54+
ctx: The command context.
55+
attachment: Attachment to include in the close message.
56+
message: Close message sent to the recipient(s).
5657
5758
Raises:
58-
RuntimeError: If an impossible situation is encountered.
59+
RuntimeError: When invoked outside a recognized ticket channel.
5960
"""
6061
if not isinstance(ctx.channel, discord.TextChannel | discord.Thread):
6162
raise RuntimeError("Command invoked in a non-text channel, which should be impossible.")

modmail/cogs/modmail/commands/reply.py

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import discord
1313

14-
from modmail.core import Context, _, in_modmail_ticket, lazy_hybrid_command, staff_only, wrap
14+
from modmail.core import Context, ParamInfo, _, bot_command, in_modmail_ticket, staff_only
1515

1616
if TYPE_CHECKING:
1717
from .. import Modmail
@@ -22,19 +22,20 @@
2222

2323

2424
@staff_only
25-
@wrap(
26-
discord.app_commands.rename,
27-
attachment=_("ftl-cmd-reply-param-attachment-name"),
28-
message=_("ftl-cmd-reply-param-message-name"),
29-
)
30-
@wrap(
31-
discord.app_commands.describe,
32-
attachment=_("ftl-cmd-reply-param-attachment-description"),
33-
message=_("ftl-cmd-reply-param-message-description"),
34-
)
35-
@lazy_hybrid_command(
25+
@bot_command(
3626
name=_("ftl-cmd-reply-name"),
3727
description=_("ftl-cmd-reply-description"),
28+
help=_("ftl-cmd-reply-help"),
29+
param_info={
30+
"attachment": ParamInfo(
31+
name=_("ftl-cmd-reply-param-attachment-name"),
32+
description=_("ftl-cmd-reply-param-attachment-description"),
33+
),
34+
"message": ParamInfo(
35+
name=_("ftl-cmd-reply-param-message-name"),
36+
description=_("ftl-cmd-reply-param-message-description"),
37+
),
38+
},
3839
)
3940
@in_modmail_ticket()
4041
async def reply_command(
@@ -44,22 +45,18 @@ async def reply_command(
4445
*,
4546
message: str = "",
4647
) -> None:
47-
"""Reply to a ticket in Modmail.
48-
49-
This command allows staff members to reply to a ticket in Modmail. It checks if the
50-
command is invoked in the correct channel and sends the message to the ticket.
48+
"""Send a reply to the ticket's recipient(s).
5149
52-
The attachment parameter is used to send files along with the message for slash commands.
53-
It is automatically parsed by discord.py and injected into ctx.message.
50+
At least one of `message` or `attachment` must be provided.
5451
5552
Args:
5653
cog: The Modmail cog instance.
57-
ctx: The command context containing information about the invocation.
58-
attachment: An optional attachment to include in the reply. Auto parsed by discord.py.
59-
message: The message to send as a reply.
54+
ctx: The command context.
55+
attachment: File to include in the reply.
56+
message: Reply text sent to the recipient(s).
6057
6158
Raises:
62-
RuntimeError: If an impossible situation is encountered.
59+
RuntimeError: When invoked outside a recognized ticket channel.
6360
"""
6461
if not isinstance(ctx.channel, discord.TextChannel | discord.Thread):
6562
raise RuntimeError("Command invoked in a non-text channel, which should be impossible.")

modmail/cogs/modmail/commands/sclose.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import discord
1313

14-
from modmail.core import Context, _, in_modmail_ticket, lazy_hybrid_command, staff_only, wrap
14+
from modmail.core import Context, ParamInfo, _, bot_command, in_modmail_ticket, staff_only
1515
from modmail.enum import TicketMessageType, TicketStatus
1616

1717
if TYPE_CHECKING:
@@ -23,19 +23,20 @@
2323

2424

2525
@staff_only
26-
@wrap(
27-
discord.app_commands.rename,
28-
attachment=_("ftl-cmd-sclose-param-attachment-name"),
29-
message=_("ftl-cmd-sclose-param-message-name"),
30-
)
31-
@wrap(
32-
discord.app_commands.describe,
33-
attachment=_("ftl-cmd-sclose-param-attachment-description"),
34-
message=_("ftl-cmd-sclose-param-message-description"),
35-
)
36-
@lazy_hybrid_command(
26+
@bot_command(
3727
name=_("ftl-cmd-sclose-name"),
3828
description=_("ftl-cmd-sclose-description"),
29+
help=_("ftl-cmd-sclose-help"),
30+
param_info={
31+
"attachment": ParamInfo(
32+
name=_("ftl-cmd-sclose-param-attachment-name"),
33+
description=_("ftl-cmd-sclose-param-attachment-description"),
34+
),
35+
"message": ParamInfo(
36+
name=_("ftl-cmd-sclose-param-message-name"),
37+
description=_("ftl-cmd-sclose-param-message-description"),
38+
),
39+
},
3940
)
4041
@in_modmail_ticket()
4142
async def sclose_command(
@@ -45,16 +46,18 @@ async def sclose_command(
4546
*,
4647
message: str = "",
4748
) -> None:
48-
"""Silently close a ticket in Modmail.
49+
"""Close the current ticket without notifying the recipient(s).
50+
51+
Any `message` or `attachment` is saved in the ticket log but not delivered.
4952
5053
Args:
5154
cog: The Modmail cog instance.
52-
ctx: The command context containing information about the invocation.
53-
attachment: An optional attachment to include in the close message. Auto parsed by discord.py.
54-
message: The message to save as a close message (not sent to recipient).
55+
ctx: The command context.
56+
attachment: Attachment to log (not sent to recipients).
57+
message: Note saved to the ticket log, not delivered to recipients.
5558
5659
Raises:
57-
RuntimeError: If an impossible situation is encountered.
60+
RuntimeError: When invoked outside a recognized ticket channel.
5861
"""
5962
if not isinstance(ctx.channel, discord.TextChannel | discord.Thread):
6063
raise RuntimeError("Command invoked in a non-text channel, which should be impossible.")

0 commit comments

Comments
 (0)