Add ConfirmView utility (#6176)

Co-authored-by: Jakub Kuczys <me@jacken.men>
This commit is contained in:
TrustyJAID 2023-06-21 10:47:47 -06:00 committed by GitHub
parent 49bf103891
commit 7dff136937
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 247 additions and 1 deletions

View File

@ -84,6 +84,65 @@ Utility UI
.. automodule:: redbot.core.utils.views .. automodule:: redbot.core.utils.views
:members: :members:
:exclude-members: ConfirmView
.. autoclass:: ConfirmView
:members:
:exclude-members: confirm_button, dismiss_button
.. autoattribute:: confirm_button
:no-value:
A `discord.ui.Button` to confirm the message.
The button's callback will set `result` to ``True``, defer the response,
and call `on_timeout()` to clean up the view.
.. rubric:: Example
Changing the style and label of this `discord.ui.Button`::
view = ConfirmView(ctx.author)
view.confirm_button.style = discord.ButtonStyle.red
view.confirm_button.label = "Delete"
view.dismiss_button.label = "Cancel"
view.message = await ctx.send(
"Are you sure you want to remove #very-important-channel?", view=view
)
await view.wait()
if view.result:
await ctx.send("Channel #very-important-channel deleted.")
else:
await ctx.send("Canceled.")
:type: discord.ui.Button
.. autoattribute:: dismiss_button
:no-value:
A `discord.ui.Button` to dismiss the message.
The button's callback will set `result` to ``False``, defer the response,
and call `on_timeout()` to clean up the view.
.. rubric:: Example
Changing the style and label of this `discord.ui.Button`::
view = ConfirmView(ctx.author)
view.confirm_button.style = discord.ButtonStyle.red
view.confirm_button.label = "Delete"
view.dismiss_button.label = "Cancel"
view.message = await ctx.send(
"Are you sure you want to remove #very-important-channel?", view=view
)
await view.wait()
if view.result:
await ctx.send("Channel #very-important-channel deleted.")
else:
await ctx.send("Canceled.")
:type: discord.ui.Button
AntiSpam AntiSpam
======== ========

View File

@ -11,7 +11,7 @@ from redbot.core.commands.converter import get_dict_converter
if TYPE_CHECKING: if TYPE_CHECKING:
from redbot.core.commands import Context from redbot.core.commands import Context
__all__ = ("SimpleMenu", "SetApiModal", "SetApiView") __all__ = ("SimpleMenu", "SetApiModal", "SetApiView", "ConfirmView")
_ = Translator("UtilsViews", __file__) _ = Translator("UtilsViews", __file__)
@ -431,3 +431,190 @@ class SetApiView(discord.ui.View):
return await interaction.response.send_modal( return await interaction.response.send_modal(
SetApiModal(self.default_service, self.default_keys) SetApiModal(self.default_service, self.default_keys)
) )
class ConfirmView(discord.ui.View):
"""
A simple `discord.ui.View` used for confirming something.
Parameters
----------
author: Optional[discord.abc.User]
The user who you want to be interacting with the confirmation.
If this is omitted anyone can click yes or no.
timeout: float
The timeout of the view in seconds. Defaults to ``180`` seconds.
disable_buttons: bool
Whether to disable the buttons instead of removing them from the message after the timeout.
Defaults to ``False``.
Examples
--------
Using the view::
view = ConfirmView(ctx.author)
# attach the message to the view after sending it.
# This way, the view will be automatically removed
# from the message after the timeout.
view.message = await ctx.send("Are you sure you about that?", view=view)
await view.wait()
if view.result:
await ctx.send("Okay I will do that.")
else:
await ctx.send("I will not be doing that then.")
Auto-disable the buttons after timeout if nothing is pressed::
view = ConfirmView(ctx.author, disable_buttons=True)
view.message = await ctx.send("Are you sure you about that?", view=view)
await view.wait()
if view.result:
await ctx.send("Okay I will do that.")
else:
await ctx.send("I will not be doing that then.")
Attributes
----------
result: Optional[bool]
The result of the confirm view.
author: Optional[discord.abc.User]
The author of the message who is allowed to press the buttons.
message: Optional[discord.Message]
The message the confirm view is sent on. This can be set while
sending the message. This can also be left as ``None`` in which case
nothing will happen in `on_timeout()`, if the view is never interacted with.
disable_buttons: bool
Whether to disable the buttons isntead of removing them on timeout
(if the `message` attribute has been set on the view).
"""
def __init__(
self,
author: Optional[discord.abc.User] = None,
*,
timeout: float = 180.0,
disable_buttons: bool = False,
):
if timeout is None:
raise TypeError("This view should not be used as a persistent view.")
super().__init__(timeout=timeout)
self.result: Optional[bool] = None
self.author: Optional[discord.abc.User] = author
self.message: Optional[discord.Message] = None
self.disable_buttons = disable_buttons
async def on_timeout(self):
"""
A callback that is called by the provided (default) callbacks for `confirm_button`
and `dismiss_button` as well as when a views timeout elapses without being
explicitly stopped.
The default implementation will either disable the buttons
when `disable_buttons` is ``True``, or remove the view from the message otherwise.
.. note::
This will not do anything if `message` is ``None``.
"""
if self.message is None:
# we can't do anything here if message is none
return
if self.disable_buttons:
self.confirm_button.disabled = True
self.dismiss_button.disabled = True
await self.message.edit(view=self)
else:
await self.message.edit(view=None)
@discord.ui.button(label=_("Yes"), style=discord.ButtonStyle.green)
async def confirm_button(self, interaction: discord.Interaction, button: discord.ui.Button):
# Warning: The Sphinx documentation for this method/attribute does not use this docstring.
"""
A `discord.ui.Button` to confirm the message.
The button's callback will set `result` to ``True``, defer the response,
and call `on_timeout()` to clean up the view.
Example
-------
Changing the style and label of this `discord.ui.Button`::
view = ConfirmView(ctx.author)
view.confirm_button.style = discord.ButtonStyle.red
view.confirm_button.label = "Delete"
view.dismiss_button.label = "Cancel"
view.message = await ctx.send(
"Are you sure you want to remove #very-important-channel?", view=view
)
await view.wait()
if view.result:
await ctx.send("Channel #very-important-channel deleted.")
else:
await ctx.send("Canceled.")
"""
self.result = True
self.stop()
# respond to the interaction so the user does not see "interaction failed".
await interaction.response.defer()
# call `on_timeout` explicitly here since it's not called when `stop()` is called.
await self.on_timeout()
@discord.ui.button(label=_("No"), style=discord.ButtonStyle.secondary)
async def dismiss_button(self, interaction: discord.Interaction, button: discord.ui.Button):
# Warning: The Sphinx documentation for this method/attribute does not use this docstring.
"""
A `discord.ui.Button` to dismiss the message.
The button's callback will set `result` to ``False``, defer the response,
and call `on_timeout()` to clean up the view.
Example
-------
Changing the style and label of this `discord.ui.Button`::
view = ConfirmView(ctx.author)
view.confirm_button.style = discord.ButtonStyle.red
view.confirm_button.label = "Delete"
view.dismiss_button.label = "Cancel"
view.message = await ctx.send(
"Are you sure you want to remove #very-important-channel?", view=view
)
await view.wait()
if view.result:
await ctx.send("Channel #very-important-channel deleted.")
else:
await ctx.send("Canceled.")
"""
self.result = False
self.stop()
# respond to the interaction so the user does not see "interaction failed".
await interaction.response.defer()
# call `on_timeout` explicitly here since it's not called when `stop()` is called.
await self.on_timeout()
async def interaction_check(self, interaction: discord.Interaction):
"""
A callback that is called when an interaction happens within the view
that checks whether the view should process item callbacks for the interaction.
The default implementation of this will assign value of `discord.Interaction.message`
to the `message` attribute and either:
- send an ephemeral failure message and return ``False``,
if `author` is set and isn't the same as the interaction user, or
- return ``True``
.. seealso::
The documentation of the callback in the base class:
:meth:`discord.ui.View.interaction_check()`
"""
if self.message is None:
self.message = interaction.message
if self.author and interaction.user.id != self.author.id:
await interaction.response.send_message(
content=_("You are not authorized to interact with this."), ephemeral=True
)
return False
return True