Skip to content

Commit 261b732

Browse files
authored
feat: Support for menu in interaction response (#27)
1 parent 792eb11 commit 261b732

File tree

5 files changed

+121
-16
lines changed

5 files changed

+121
-16
lines changed

docs/ext/menus/menu_examples.rst

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,39 @@ the :class:`ButtonMenu` in the same way as shown before.
171171
async def button_confirm(ctx):
172172
confirm = await ButtonConfirm("Confirm?").prompt(ctx)
173173
await ctx.send(f"You said: {confirm}")
174+
175+
176+
Slash Commands
177+
--------------
178+
179+
To use a menu in a slash command or component response, we need to pass ``interaction`` to :meth:`start() <Menu.start>` instead of ``ctx``.
180+
181+
``interaction`` must be passed as a keyword argument.
182+
183+
Additionally, we will use :meth:`interaction.response.send_message() <nextcord.InteractionResponse.send_message>`
184+
in the :meth:`send_initial_message() <Menu.send_initial_message>` method to send the initial message.
185+
186+
To make the response message ephemeral, we can pass ``ephemeral=True`` to :meth:`start() <Menu.start>` as well.
187+
188+
.. code:: py
189+
190+
class MySlashButtonMenu(menus.ButtonMenu):
191+
async def send_initial_message(self, ctx, channel):
192+
await self.interaction.response.send_message(f"Hello {self.interaction.user}", view=self)
193+
return await self.interaction.original_message()
194+
195+
@nextcord.ui.button(emoji="\N{THUMBS UP SIGN}")
196+
async def on_thumbs_up(self, button, interaction):
197+
await self.message.edit(content=f"Thanks {interaction.user}!")
198+
199+
@nextcord.ui.button(emoji="\N{THUMBS DOWN SIGN}")
200+
async def on_thumbs_down(self, button, interaction):
201+
await self.message.edit(content=f"That's not nice {interaction.user}...")
202+
203+
@nextcord.ui.button(emoji="\N{BLACK SQUARE FOR STOP}\ufe0f")
204+
async def on_stop(self, button, interaction):
205+
self.stop()
206+
207+
@bot.slash_command(guild_ids=[TESTING_GUILD_ID], name="slashmenu")
208+
async def slash_menu_example(interaction: nextcord.Interaction):
209+
await MySlashButtonMenu().start(interaction=interaction)

docs/ext/menus/pagination_examples.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,22 @@ for an example on how to create a :class:`Select Menu <nextcord.ui.Select>`.
325325
pages = SelectButtonMenuPages(source=MySource(data))
326326
await pages.start(ctx)
327327
328+
Menu in Slash Command Response
329+
------------------------------
330+
331+
To use a menu in a slash command or component response, we need to pass ``interaction`` to
332+
:meth:`start() <Menu.start>` as a keyword argument instead of ``ctx``.
333+
334+
To make the response message ephemeral, we can pass ``ephemeral=True`` to :meth:`start() <Menu.start>` as well.
335+
336+
.. code:: py
337+
338+
@bot.slash_command(guild_ids=[TEST_GUILD_ID], name="slashpages")
339+
async def slash_pages(interaction: nextcord.Interaction):
340+
data = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
341+
pages = menus.ButtonMenuPages(source=MySource(data))
342+
await pages.start(interaction=interaction)
343+
328344
Paginated Help Command Cog
329345
--------------------------
330346

nextcord/ext/menus/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
from .utils import *
77

88
# Needed for the setup.py script
9-
__version__ = "1.3.4"
9+
__version__ = "1.4.0"

nextcord/ext/menus/menu_pages.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,17 +110,34 @@ async def send_initial_message(
110110
"""
111111
page = await self._source.get_page(0)
112112
kwargs = await self._get_kwargs_from_page(page)
113+
# filter out kwargs that are "None"
114+
kwargs = {k: v for k, v in kwargs.items() if v is not None}
115+
# if there is an interaction, send an interaction response
116+
if self.interaction is not None:
117+
await self.interaction.response.send_message(
118+
ephemeral=self.ephemeral, **kwargs
119+
)
120+
return await self.interaction.original_message()
121+
# otherwise, send the message using the channel
113122
return await channel.send(**kwargs)
114123

115124
async def start(
116125
self,
117-
ctx: commands.Context,
126+
ctx: Optional[commands.Context] = None,
127+
interaction: Optional[nextcord.Interaction] = None,
118128
*,
119129
channel: Optional[nextcord.abc.Messageable] = None,
120-
wait: Optional[bool] = False
130+
wait: bool = False,
131+
ephemeral: bool = False,
121132
):
122133
await self._source._prepare_once()
123-
await super().start(ctx, channel=channel, wait=wait)
134+
await super().start(
135+
ctx=ctx,
136+
interaction=interaction,
137+
channel=channel,
138+
wait=wait,
139+
ephemeral=ephemeral,
140+
)
124141
# If we're not paginating, we can remove the pagination buttons
125142
if not self._source.is_paginating():
126143
await self.clear()
@@ -267,7 +284,7 @@ def __init__(
267284
self,
268285
source: PageSource,
269286
style: nextcord.ButtonStyle = nextcord.ButtonStyle.secondary,
270-
**kwargs
287+
**kwargs,
271288
):
272289
self.__button_menu_pages__ = True
273290
# make button pagination disable buttons on stop by default unless it's overridden

nextcord/ext/menus/menus.py

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,10 @@ class Menu(metaclass=_MenuMeta):
240240
Whether to verify embed permissions as well.
241241
ctx: Optional[:class:`commands.Context`]
242242
The context that started this pagination session or ``None`` if it hasn't
243-
been started yet.
243+
been started yet or :class:`nextcord.Interaction` is used instead.
244+
interaction: Optional[:class:`nextcord.Interaction`]
245+
The interaction that started this pagination session or ``None`` if it hasn't
246+
been started yet or :class:`commands.Context` is used instead.
244247
bot: Optional[:class:`commands.Bot`]
245248
The bot that is running this pagination session or ``None`` if it hasn't
246249
been started yet.
@@ -249,6 +252,9 @@ class Menu(metaclass=_MenuMeta):
249252
message of :meth:`send_initial_message`. You can set it in order to avoid
250253
calling :meth:`send_initial_message`\, if for example you have a pre-existing
251254
message you want to attach a menu to.
255+
ephemeral: :class:`bool`
256+
Whether to make the response ephemeral when using an interaction response.
257+
Note: Ephemeral messages do not support reactions.
252258
"""
253259

254260
def __init__(
@@ -270,6 +276,8 @@ def __init__(
270276
self._running = True
271277
self.message = message
272278
self.ctx = None
279+
self.interaction = None
280+
self.ephemeral = False
273281
self.bot = None
274282
self._author_id = None
275283
self._buttons = self.__class__.get_buttons()
@@ -602,32 +610,47 @@ async def on_menu_button_error(self, exc: Exception):
602610

603611
async def start(
604612
self,
605-
ctx: commands.Context,
613+
ctx: Optional[commands.Context] = None,
614+
interaction: Optional[nextcord.Interaction] = None,
606615
*,
607616
channel: Optional[nextcord.abc.Messageable] = None,
608-
wait: bool = False
617+
wait: bool = False,
618+
ephemeral: bool = False,
609619
):
610620
"""|coro|
611621
612622
Starts the interactive menu session.
613623
624+
To start a menu session, you must provide either a
625+
:class:`Context <nextcord.ext.commands.Context>` or an :class:`Interaction <nextcord.Interaction>` object.
626+
614627
Parameters
615628
-----------
616-
ctx: :class:`Context`
629+
ctx: :class:`Context <nextcord.ext.commands.Context>`
617630
The invocation context to use.
631+
interaction: :class:`nextcord.Interaction`
632+
The interaction context to use for slash and
633+
component responses.
618634
channel: :class:`nextcord.abc.Messageable`
619635
The messageable to send the message to. If not given
620-
then it defaults to the channel in the context.
636+
then it defaults to the channel in the context
637+
or interaction.
621638
wait: :class:`bool`
622639
Whether to wait until the menu is completed before
623640
returning back to the caller.
641+
ephemeral: :class:`bool`
642+
Whether to make the response ephemeral when using an
643+
interaction response. Note: ephemeral messages do not
644+
support reactions.
624645
625646
Raises
626647
-------
627648
MenuError
628649
An error happened when verifying permissions.
629650
nextcord.HTTPException
630651
Adding a reaction failed.
652+
ValueError
653+
No context or interaction was given or both were given.
631654
"""
632655

633656
# Clear the reaction buttons cache and re-compute if possible.
@@ -636,11 +659,24 @@ async def start(
636659
except AttributeError:
637660
pass
638661

639-
self.bot = bot = ctx.bot
662+
# ensure only one of ctx and interaction is set
663+
if ctx is None and interaction is None:
664+
raise ValueError("ctx or interaction must be set.")
665+
if ctx is not None and interaction is not None:
666+
raise ValueError("ctx and interaction cannot both be set.")
667+
640668
self.ctx = ctx
641-
self._author_id = ctx.author.id
642-
channel = channel or ctx.channel
643-
me = channel.guild.me if hasattr(channel, "guild") else ctx.bot.user
669+
self.interaction = interaction
670+
self.ephemeral = ephemeral
671+
if ctx is not None:
672+
self.bot = ctx.bot
673+
self._author_id = ctx.author.id
674+
channel = channel or ctx.channel
675+
else:
676+
self.bot = getattr(interaction, "client", interaction._state._get_client())
677+
self._author_id = interaction.user.id
678+
channel = channel or interaction.channel
679+
me = channel.guild.me if hasattr(channel, "guild") else self.bot.user
644680
permissions = channel.permissions_for(me)
645681
self.__me = nextcord.Object(id=me.id)
646682
self._verify_permissions(ctx, channel, permissions)
@@ -656,13 +692,13 @@ async def start(
656692
self.__tasks.clear()
657693

658694
self._running = True
659-
self.__tasks.append(bot.loop.create_task(self._internal_loop()))
695+
self.__tasks.append(self.bot.loop.create_task(self._internal_loop()))
660696

661697
async def add_reactions_task():
662698
for emoji in self.buttons:
663699
await msg.add_reaction(emoji)
664700

665-
self.__tasks.append(bot.loop.create_task(add_reactions_task()))
701+
self.__tasks.append(self.bot.loop.create_task(add_reactions_task()))
666702

667703
if wait:
668704
await self._event.wait()

0 commit comments

Comments
 (0)