Warning

This document is for Red's development version, which can be significantly different from previous releases. If you're a regular user, you should read the Red documentation for the current stable release.

Source code for redbot.core.utils.menus

# Original source of reaction-based menu idea from
# https://github.com/Lunar-Dust/Dusty-Cogs/blob/master/menu/menu.py
#
# Ported to Red V3 by Palm\_\_ (https://github.com/palmtree5)
import asyncio
import contextlib
import functools
from types import MappingProxyType
from typing import Callable, Dict, Iterable, List, Mapping, Optional, TypeVar, Union

import discord

from .. import commands
from .predicates import ReactionPredicate
from .views import SimpleMenu, _SimplePageSource

__all__ = (
    "menu",
    "next_page",
    "prev_page",
    "close_menu",
    "start_adding_reactions",
    "DEFAULT_CONTROLS",
)

_T = TypeVar("_T")
_PageList = TypeVar("_PageList", List[str], List[discord.Embed])
_ReactableEmoji = Union[str, discord.Emoji]
_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: discord.PartialEmoji, func: _ControlCallable):
        super().__init__(emoji=emoji, style=discord.ButtonStyle.grey)
        self.func = func

    async def callback(self, interaction: discord.Interaction):
        await interaction.response.defer()
        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 = (
            str(self.emoji)
            if self.emoji.is_unicode_emoji()
            else (ctx.bot.get_emoji(self.emoji.id) or self.emoji)
        )
        user = self.view.author if not self.view._fallback_author_to_ctx else None
        if user is not None:
            await self.func(ctx, pages, controls, message, page, timeout, emoji, user=user)
        else:
            await self.func(ctx, pages, controls, message, page, timeout, emoji)





[docs]async def next_page( ctx: commands.Context, pages: list, controls: Mapping[str, _ControlCallable], message: discord.Message, page: int, timeout: float, emoji: str, *, user: Optional[discord.User] = None, ) -> _T: """ Function for showing next page which is suitable for use in ``controls`` mapping that is passed to `menu()`. """ if page >= len(pages) - 1: page = 0 # Loop around to the first item else: page = page + 1 if user is not None: return await menu( ctx, pages, controls, message=message, page=page, timeout=timeout, user=user ) else: return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout)
[docs]async def prev_page( ctx: commands.Context, pages: list, controls: Mapping[str, _ControlCallable], message: discord.Message, page: int, timeout: float, emoji: str, *, user: Optional[discord.User] = None, ) -> _T: """ Function for showing previous page which is suitable for use in ``controls`` mapping that is passed to `menu()`. """ if page <= 0: page = len(pages) - 1 # Loop around to the last item else: page = page - 1 if user is not None: return await menu( ctx, pages, controls, message=message, page=page, timeout=timeout, user=user ) else: return await menu(ctx, pages, controls, message=message, page=page, timeout=timeout)
[docs]async def close_menu( ctx: commands.Context, pages: list, controls: Mapping[str, _ControlCallable], message: discord.Message, page: int, timeout: float, emoji: str, *, user: Optional[discord.User] = None, ) -> None: """ Function for closing (deleting) menu which is suitable for use in ``controls`` mapping that is passed to `menu()`. """ with contextlib.suppress(discord.NotFound): await message.delete()
[docs]def start_adding_reactions( message: discord.Message, emojis: Iterable[_ReactableEmoji] ) -> asyncio.Task: """Start adding reactions to a message. This is a non-blocking operation - calling this will schedule the reactions being added, but the calling code will continue to execute asynchronously. There is no need to await this function. This is particularly useful if you wish to start waiting for a reaction whilst the reactions are still being added - in fact, this is exactly what `menu()` uses to do that. Parameters ---------- message: discord.Message The message to add reactions to. emojis : Iterable[Union[str, discord.Emoji]] The emojis to react to the message with. Returns ------- asyncio.Task The task for the coroutine adding the reactions. """ async def task(): # The task should exit silently if the message is deleted with contextlib.suppress(discord.NotFound): for emoji in emojis: await message.add_reaction(emoji) return asyncio.create_task(task())
#: Default controls for `menu()` that contain controls for #: previous page, closing menu, and next page. DEFAULT_CONTROLS: Mapping[str, _ControlCallable] = MappingProxyType( { "\N{LEFTWARDS BLACK ARROW}\N{VARIATION SELECTOR-16}": prev_page, "\N{CROSS MARK}": close_menu, "\N{BLACK RIGHTWARDS ARROW}\N{VARIATION SELECTOR-16}": next_page, } )