mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 11:18:54 -05:00
Add global buttons to base menus (#5683)
Co-authored-by: Kowlin <10947836+Kowlin@users.noreply.github.com>
This commit is contained in:
parent
aaeb1b5daa
commit
b0a3f00f41
@ -35,7 +35,7 @@ Embed Helpers
|
|||||||
:members:
|
:members:
|
||||||
:exclude-members: randomize_color
|
:exclude-members: randomize_color
|
||||||
|
|
||||||
Reaction Menus
|
Menus
|
||||||
==============
|
==============
|
||||||
|
|
||||||
.. automodule:: redbot.core.utils.menus
|
.. automodule:: redbot.core.utils.menus
|
||||||
|
|||||||
@ -142,6 +142,7 @@ class Red(
|
|||||||
schema_version=0,
|
schema_version=0,
|
||||||
datarequests__allow_user_requests=True,
|
datarequests__allow_user_requests=True,
|
||||||
datarequests__user_requests_are_strict=True,
|
datarequests__user_requests_are_strict=True,
|
||||||
|
use_buttons=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._config.register_guild(
|
self._config.register_guild(
|
||||||
@ -1351,6 +1352,17 @@ class Red(
|
|||||||
global_setting = await self._config.embeds()
|
global_setting = await self._config.embeds()
|
||||||
return global_setting
|
return global_setting
|
||||||
|
|
||||||
|
async def use_buttons(self) -> bool:
|
||||||
|
"""
|
||||||
|
Determines whether the bot owner has enabled use of buttons instead of
|
||||||
|
reactions for basic menus.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
"""
|
||||||
|
return await self._config.use_buttons()
|
||||||
|
|
||||||
async def is_owner(self, user: Union[discord.User, discord.Member], /) -> bool:
|
async def is_owner(self, user: Union[discord.User, discord.Member], /) -> bool:
|
||||||
"""
|
"""
|
||||||
Determines if the user should be considered a bot owner.
|
Determines if the user should be considered a bot owner.
|
||||||
|
|||||||
@ -3621,6 +3621,31 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
|
|||||||
else:
|
else:
|
||||||
await ctx.send(_("Server prefixes set."))
|
await ctx.send(_("Server prefixes set."))
|
||||||
|
|
||||||
|
@_set.command(name="usebuttons")
|
||||||
|
async def use_buttons(self, ctx: commands.Context, use_buttons: bool = None):
|
||||||
|
"""
|
||||||
|
Set a global bot variable for using buttons in menus.
|
||||||
|
|
||||||
|
When enabled, all usage of cores menus API will use buttons instead of reactions.
|
||||||
|
|
||||||
|
This defaults to False.
|
||||||
|
Using this without a setting will toggle.
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `[p]set usebuttons True` - Enables using buttons.
|
||||||
|
- `[p]helpset usebuttons` - Toggles the value.
|
||||||
|
|
||||||
|
**Arguments:**
|
||||||
|
- `[use_buttons]` - Whether to use buttons. Leave blank to toggle.
|
||||||
|
"""
|
||||||
|
if use_buttons is None:
|
||||||
|
use_buttons = not await ctx.bot._config.use_buttons()
|
||||||
|
await ctx.bot._config.use_buttons.set(use_buttons)
|
||||||
|
if use_buttons:
|
||||||
|
await ctx.send(_("I will use buttons on basic menus."))
|
||||||
|
else:
|
||||||
|
await ctx.send(_("I will not use buttons on basic menus."))
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def helpset(self, ctx: commands.Context):
|
async def helpset(self, ctx: commands.Context):
|
||||||
|
|||||||
@ -6,18 +6,43 @@ import asyncio
|
|||||||
import contextlib
|
import contextlib
|
||||||
import functools
|
import functools
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
from typing import Callable, Iterable, List, Mapping, Optional, TypeVar, Union
|
from typing import Callable, Dict, Iterable, List, Mapping, Optional, TypeVar, Union
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
from .. import commands
|
from .. import commands
|
||||||
from .predicates import ReactionPredicate
|
from .predicates import ReactionPredicate
|
||||||
|
from .views import SimpleMenu, _SimplePageSource
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
_PageList = TypeVar("_PageList", List[str], List[discord.Embed])
|
_PageList = TypeVar("_PageList", List[str], List[discord.Embed])
|
||||||
_ReactableEmoji = Union[str, discord.Emoji]
|
_ReactableEmoji = Union[str, discord.Emoji]
|
||||||
_ControlCallable = Callable[[commands.Context, _PageList, discord.Message, int, float, str], _T]
|
_ControlCallable = Callable[[commands.Context, _PageList, discord.Message, int, float, str], _T]
|
||||||
|
|
||||||
|
_active_menus: Dict[int, SimpleMenu] = {}
|
||||||
|
|
||||||
|
|
||||||
|
class _GenericButton(discord.ui.Button):
|
||||||
|
def __init__(self, emoji: Union[str, discord.PartialEmoji], func):
|
||||||
|
super().__init__(
|
||||||
|
emoji=discord.PartialEmoji.from_str(emoji), style=discord.ButtonStyle.grey
|
||||||
|
)
|
||||||
|
self.func = func
|
||||||
|
|
||||||
|
async def callback(self, interaction: discord.Interaction):
|
||||||
|
ctx = self.view.ctx
|
||||||
|
pages = self.view.source.entries
|
||||||
|
controls = None
|
||||||
|
message = self.view.message
|
||||||
|
page = self.view.current_page
|
||||||
|
timeout = self.view.timeout
|
||||||
|
emoji = self.emoji
|
||||||
|
try:
|
||||||
|
await self.func(ctx, pages, controls, message, page, timeout, emoji)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
await interaction.response.defer()
|
||||||
|
|
||||||
|
|
||||||
async def menu(
|
async def menu(
|
||||||
ctx: commands.Context,
|
ctx: commands.Context,
|
||||||
@ -64,6 +89,18 @@ async def menu(
|
|||||||
RuntimeError
|
RuntimeError
|
||||||
If either of the notes above are violated
|
If either of the notes above are violated
|
||||||
"""
|
"""
|
||||||
|
if message is not None and message.id in _active_menus:
|
||||||
|
# prevents the expected callback from going any further
|
||||||
|
# our custom button will always pass the message the view is
|
||||||
|
# attached to, allowing one to send multiple menus on the same
|
||||||
|
# context.
|
||||||
|
view = _active_menus[message.id]
|
||||||
|
if pages != view.source.entries:
|
||||||
|
view._source = _SimplePageSource(pages)
|
||||||
|
new_page = await view.get_page(page)
|
||||||
|
view.current_page = page
|
||||||
|
await view.message.edit(**new_page)
|
||||||
|
return
|
||||||
if not isinstance(pages[0], (discord.Embed, str)):
|
if not isinstance(pages[0], (discord.Embed, str)):
|
||||||
raise RuntimeError("Pages must be of type discord.Embed or str")
|
raise RuntimeError("Pages must be of type discord.Embed or str")
|
||||||
if not all(isinstance(x, discord.Embed) for x in pages) and not all(
|
if not all(isinstance(x, discord.Embed) for x in pages) and not all(
|
||||||
@ -81,6 +118,52 @@ async def menu(
|
|||||||
maybe_coro = value.func
|
maybe_coro = value.func
|
||||||
if not asyncio.iscoroutinefunction(maybe_coro):
|
if not asyncio.iscoroutinefunction(maybe_coro):
|
||||||
raise RuntimeError("Function must be a coroutine")
|
raise RuntimeError("Function must be a coroutine")
|
||||||
|
|
||||||
|
if await ctx.bot.use_buttons() and message is None:
|
||||||
|
# Only send the button version if `message` is None
|
||||||
|
# This is because help deals with this menu in weird ways
|
||||||
|
# where the original message is already sent prior to starting.
|
||||||
|
# This is not normally the way we recommend sending this because
|
||||||
|
# internally we already include the emojis we expect.
|
||||||
|
if controls == DEFAULT_CONTROLS:
|
||||||
|
view = SimpleMenu(pages)
|
||||||
|
await view.start(ctx)
|
||||||
|
await view.wait()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
view = SimpleMenu(pages)
|
||||||
|
view.remove_item(view.last_button)
|
||||||
|
view.remove_item(view.first_button)
|
||||||
|
has_next = False
|
||||||
|
has_prev = False
|
||||||
|
has_close = False
|
||||||
|
to_add = {}
|
||||||
|
for emoji, func in controls.items():
|
||||||
|
if func == next_page:
|
||||||
|
has_next = True
|
||||||
|
if emoji != view.forward_button.emoji:
|
||||||
|
view.forward_button.emoji = discord.PartialEmoji.from_str(emoji)
|
||||||
|
elif func == prev_page:
|
||||||
|
has_prev = True
|
||||||
|
if emoji != view.backward_button.emoji:
|
||||||
|
view.backward_button.emoji = discord.PartialEmoji.from_str(emoji)
|
||||||
|
elif func == close_menu:
|
||||||
|
has_close = True
|
||||||
|
else:
|
||||||
|
to_add[emoji] = func
|
||||||
|
if not has_next:
|
||||||
|
view.remove_item(view.forward_button)
|
||||||
|
if not has_prev:
|
||||||
|
view.remove_item(view.backward_button)
|
||||||
|
if not has_close:
|
||||||
|
view.remove_item(view.stop_button)
|
||||||
|
for emoji, func in to_add.items():
|
||||||
|
view.add_item(_GenericButton(emoji, func))
|
||||||
|
await view.start(ctx)
|
||||||
|
_active_menus[view.message.id] = view
|
||||||
|
await view.wait()
|
||||||
|
del _active_menus[view.message.id]
|
||||||
|
return
|
||||||
current_page = pages[page]
|
current_page = pages[page]
|
||||||
|
|
||||||
if not message:
|
if not message:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user