mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-05 18:58:53 -05:00
Add ConfirmView utility (#6176)
Co-authored-by: Jakub Kuczys <me@jacken.men>
This commit is contained in:
parent
49bf103891
commit
7dff136937
@ -84,6 +84,65 @@ Utility UI
|
||||
|
||||
.. automodule:: redbot.core.utils.views
|
||||
: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
|
||||
========
|
||||
|
||||
@ -11,7 +11,7 @@ from redbot.core.commands.converter import get_dict_converter
|
||||
if TYPE_CHECKING:
|
||||
from redbot.core.commands import Context
|
||||
|
||||
__all__ = ("SimpleMenu", "SetApiModal", "SetApiView")
|
||||
__all__ = ("SimpleMenu", "SetApiModal", "SetApiView", "ConfirmView")
|
||||
|
||||
_ = Translator("UtilsViews", __file__)
|
||||
|
||||
@ -431,3 +431,190 @@ class SetApiView(discord.ui.View):
|
||||
return await interaction.response.send_modal(
|
||||
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 view’s 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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user