Exemplo n.º 1
0
class ResultMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(self, **kwargs):
        super().__init__(
            **kwargs,
            timeout=60,
            clear_reactions_after=True,
            delete_message_after=True,
        )

    def _skip_double_triangle_buttons(self):
        return super()._skip_double_triangle_buttons()

    async def finalize(self, timed_out):
        if timed_out and self.delete_message_after:
            self.delete_message_after = False

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f",
        position=menus.First(0),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_first_page(self, payload):
        """go to the first page"""
        await self.show_page(0)

    @menus.button("\N{BLACK LEFT-POINTING TRIANGLE}\ufe0f",
                  position=menus.First(1))
    async def go_to_previous_page(self, payload):
        """go to the previous page"""
        if self.current_page == 0:
            await self.show_page(self._source.get_max_pages() - 1)
        else:
            await self.show_checked_page(self.current_page - 1)

    @menus.button("\N{BLACK RIGHT-POINTING TRIANGLE}\ufe0f",
                  position=menus.Last(0))
    async def go_to_next_page(self, payload):
        """go to the next page"""
        if self.current_page == self._source.get_max_pages() - 1:
            await self.show_page(0)
        else:
            await self.show_checked_page(self.current_page + 1)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f",
        position=menus.Last(1),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_last_page(self, payload):
        """go to the last page"""
        # The call here is safe because it's guarded by skip_if
        await self.show_page(self._source.get_max_pages() - 1)

    @menus.button("\N{CROSS MARK}", position=menus.First(2))
    async def stop_pages(self, payload) -> None:
        self.stop()
Exemplo n.º 2
0
class MenuActions(menus.MenuPages, inherit_buttons=False):
    def reaction_check(self, payload):
        """The function that is used to check whether the payload should be processed.
        This is passed to :meth:`discord.ext.commands.Bot.wait_for <Bot.wait_for>`.

        There should be no reason to override this function for most users.
        This is done this way in this cog to let a bot owner operate the menu
        along with the original command invoker.

        Parameters
        ------------
        payload: :class:`discord.RawReactionActionEvent`
            The payload to check.

        Returns
        ---------
        :class:`bool`
            Whether the payload should be processed.
        """
        if payload.message_id != self.message.id:
            return False
        if payload.user_id not in (*self.bot.owner_ids, self._author_id):
            return False

        return payload.emoji in self.buttons

    async def show_checked_page(self, page_number: int) -> None:
        # This is a custom impl of show_checked_page that allows looping around back to the
        # beginning of the page stack when at the end and using next, or looping to the end
        # when at the beginning page and using prev.
        max_pages = self._source.get_max_pages()
        try:
            if max_pages is None:
                await self.show_page(page_number)
            elif page_number >= max_pages:
                await self.show_page(0)
            elif page_number < 0:
                await self.show_page(max_pages - 1)
            elif max_pages > page_number >= 0:
                await self.show_page(page_number)
        except IndexError:
            pass

    @menus.button("\N{UP-POINTING RED TRIANGLE}", position=menus.First(1))
    async def prev(self, payload: discord.RawReactionActionEvent):
        await self.show_checked_page(self.current_page - 1)

    @menus.button("\N{DOWN-POINTING RED TRIANGLE}", position=menus.First(2))
    async def next(self, payload: discord.RawReactionActionEvent):
        await self.show_checked_page(self.current_page + 1)

    @menus.button("\N{CROSS MARK}", position=menus.Last(0))
    async def close_menu(self,
                         payload: discord.RawReactionActionEvent) -> None:
        self.stop()
Exemplo n.º 3
0
class ButtonMenu(BaseButtonMenu, inherit_buttons=False):
    def _skip_single_arrows(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages == 1

    def _skip_double_triangle_buttons(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages <= 2

    @menus.button(
        LEFT_ARROW,
        position=menus.First(1),
        skip_if=_skip_single_arrows,
    )
    async def go_to_previous_page(self, button: InteractionButton):
        await self.show_checked_page(self.current_page - 1, button)

    @menus.button(
        RIGHT_ARROW,
        position=menus.Last(0),
        skip_if=_skip_single_arrows,
    )
    async def go_to_next_page(self, button: InteractionButton):
        await self.show_checked_page(self.current_page + 1, button)

    @menus.button(
        REWIND_ARROW,
        position=menus.First(0),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_first_page(self, button: InteractionButton):
        await self.show_checked_page(0, button)

    @menus.button(
        FORWARD_ARROW,
        position=menus.Last(1),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_last_page(self, button: InteractionButton):
        await self.show_checked_page(self._source.get_max_pages() - 1, button)

    @menus.button(CLOSE_EMOJI)
    async def stop_pages(self, button: InteractionButton):
        await self.message.delete()
        # if self.clear_reactions_after:
        #    await self.close_buttons(button)
        self.stop()
Exemplo n.º 4
0
 def __init__(
     self,
     source: menus.PageSource,
     cog: commands.Cog,
     user_token: tekore.Token,
     clear_reactions_after: bool = True,
     delete_message_after: bool = False,
     timeout: int = 60,
     message: discord.Message = None,
     **kwargs: Any,
 ) -> None:
     super().__init__(
         source,
         clear_reactions_after=clear_reactions_after,
         delete_message_after=delete_message_after,
         timeout=timeout,
         message=message,
         **kwargs,
     )
     self.user_token = user_token
     self.cog = cog
     self.add_button(
         menus.Button(emoji_handler.get_emoji("next"), self.skip_next, position=menus.First(6))
     )
     self.add_button(
         menus.Button(
             emoji_handler.get_emoji("previous"), self.skip_previous, position=menus.First(0)
         )
     )
     self.add_button(
         menus.Button(
             emoji_handler.get_emoji("playpause"), self.play_pause, position=menus.First(2)
         )
     )
     self.add_button(
         menus.Button(
             emoji_handler.get_emoji("playall"),
             self.play_pause_all,
             position=menus.First(3),
             skip_if=self._skip_play_all,
         )
     )
     self.add_button(
         menus.Button(emoji_handler.get_emoji("like"), self.like_song, position=menus.First(4))
     )
     self.add_button(
         menus.Button(
             emoji_handler.get_emoji("back_left"),
             self.go_to_previous_page,
             position=menus.First(1),
         )
     )
     self.add_button(
         menus.Button(
             emoji_handler.get_emoji("play"), self.go_to_next_page, position=menus.First(5)
         )
     )
Exemplo n.º 5
0
 def __init__(self, cog, game: Connect4Game):
     self.cog = cog
     self.game = game
     super().__init__(
         timeout=self.GAME_TIMEOUT_THRESHOLD,
         delete_message_after=False,
         clear_reactions_after=True,
     )
     for index, digit in enumerate(self.DIGITS):
         self.add_button(
             menus.Button(digit, self.handle_digit_press, position=menus.First(index))
         )
Exemplo n.º 6
0
class BaseMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(
        self,
        source: menus.PageSource,
        clear_reactions_after: bool = True,
        delete_message_after: bool = False,
        timeout: int = 60,
        message: discord.Message = None,
        **kwargs: Any,
    ) -> None:
        super().__init__(
            source,
            clear_reactions_after=clear_reactions_after,
            delete_message_after=delete_message_after,
            timeout=timeout,
            message=message,
            **kwargs,
        )
        self.__tasks = self._Menu__tasks

    async def update(self, payload):
        """|coro|

        Updates the menu after an event has been received.

        Parameters
        -----------
        payload: :class:`discord.RawReactionActionEvent`
            The reaction event that triggered this update.
        """
        button = self.buttons[payload.emoji]
        if not self._running:
            return

        try:
            if button.lock:
                async with self._lock:
                    if self._running:
                        await button(self, payload)
            else:
                await button(self, payload)
        except Exception as exc:
            log.debug("Ignored exception on reaction event", exc_info=exc)

    async def start(self, ctx, *, channel=None, wait=False, page: int = 0):
        """
        Starts the interactive menu session.

        Parameters
        -----------
        ctx: :class:`Context`
            The invocation context to use.
        channel: :class:`discord.abc.Messageable`
            The messageable to send the message to. If not given
            then it defaults to the channel in the context.
        wait: :class:`bool`
            Whether to wait until the menu is completed before
            returning back to the caller.

        Raises
        -------
        MenuError
            An error happened when verifying permissions.
        discord.HTTPException
            Adding a reaction failed.
        """

        # Clear the buttons cache and re-compute if possible.
        try:
            del self.buttons
        except AttributeError:
            pass

        self.bot = bot = ctx.bot
        self.ctx = ctx
        self._author_id = ctx.author.id
        channel = channel or ctx.channel
        is_guild = isinstance(channel, discord.abc.GuildChannel)
        me = ctx.guild.me if is_guild else ctx.bot.user
        permissions = channel.permissions_for(me)
        self.__me = discord.Object(id=me.id)
        self._verify_permissions(ctx, channel, permissions)
        self._event.clear()
        msg = self.message
        if msg is None:
            self.message = msg = await self.send_initial_message(ctx,
                                                                 channel,
                                                                 page=page)
        if self.should_add_reactions():
            # Start the task first so we can listen to reactions before doing anything
            for task in self.__tasks:
                task.cancel()
            self.__tasks.clear()

            self._running = True
            self.__tasks.append(bot.loop.create_task(self._internal_loop()))

            if self.should_add_reactions():

                async def add_reactions_task():
                    for emoji in self.buttons:
                        await msg.add_reaction(emoji)

                self.__tasks.append(bot.loop.create_task(add_reactions_task()))

            if wait:
                await self._event.wait()

    async def send_initial_message(self,
                                   ctx: commands.Context,
                                   channel: discord.abc.Messageable,
                                   page: int = 0):
        """

        The default implementation of :meth:`Menu.send_initial_message`
        for the interactive pagination session.

        This implementation shows the first page of the source.
        """
        self.current_page = page
        page = await self._source.get_page(page)
        kwargs = await self._get_kwargs_from_page(page)
        return await channel.send(**kwargs)

    async def show_checked_page(self, page_number: int) -> None:
        max_pages = self._source.get_max_pages()
        try:
            if max_pages is None:
                # If it doesn't give maximum pages, it cannot be checked
                await self.show_page(page_number)
            elif page_number >= max_pages:
                await self.show_page(0)
            elif page_number < 0:
                await self.show_page(max_pages - 1)
            elif max_pages > page_number >= 0:
                await self.show_page(page_number)
        except IndexError:
            # An error happened that can be handled, so ignore it.
            pass

    def reaction_check(self, payload):
        """Just extends the default reaction_check to use owner_ids"""
        if payload.message_id != self.message.id:
            return False
        if payload.user_id not in (*self.bot.owner_ids, self._author_id):
            return False
        return payload.emoji in self.buttons

    def _skip_single_arrows(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages == 1

    def _skip_double_triangle_buttons(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages <= 2

    @menus.button(
        "\N{BLACK LEFT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}",
        position=menus.First(1),
        skip_if=_skip_single_arrows,
    )
    async def go_to_previous_page(self, payload):
        """go to the previous page"""
        await self.show_checked_page(self.current_page - 1)

    @menus.button(
        "\N{BLACK RIGHT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}",
        position=menus.Last(0),
        skip_if=_skip_single_arrows,
    )
    async def go_to_next_page(self, payload):
        """go to the next page"""
        await self.show_checked_page(self.current_page + 1)

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}",
        position=menus.First(0),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_first_page(self, payload):
        """go to the first page"""
        await self.show_page(0)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}",
        position=menus.Last(1),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_last_page(self, payload):
        """go to the last page"""
        # The call here is safe because it's guarded by skip_if
        await self.show_page(self._source.get_max_pages() - 1)

    @menus.button("\N{CROSS MARK}")
    async def stop_pages(self,
                         payload: discord.RawReactionActionEvent) -> None:
        """stops the pagination session."""
        self.stop()
Exemplo n.º 7
0
class GamesMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(
        self,
        source: menus.PageSource,
        clear_reactions_after: bool = True,
        delete_message_after: bool = False,
        timeout: int = 60,
        message: discord.Message = None,
        **kwargs: Any,
    ) -> None:
        super().__init__(
            source,
            clear_reactions_after=clear_reactions_after,
            delete_message_after=delete_message_after,
            timeout=timeout,
            message=message,
            **kwargs,
        )

    async def update(self, payload):
        """|coro|

        Updates the menu after an event has been received.

        Parameters
        -----------
        payload: :class:`discord.RawReactionActionEvent`
            The reaction event that triggered this update.
        """
        button = self.buttons[payload.emoji]
        if not self._running:
            return

        try:
            if button.lock:
                async with self._lock:
                    if self._running:
                        await button(self, payload)
            else:
                await button(self, payload)
        except Exception as exc:
            log.debug("Ignored exception on reaction event", exc_info=exc)

    async def show_page(self,
                        page_number,
                        *,
                        skip_next=False,
                        skip_prev=False):
        try:
            page = await self._source.get_page(page_number,
                                               skip_next=skip_next,
                                               skip_prev=skip_prev)
        except NoSchedule:
            team = ""
            if self.source.team:
                team = _("for {teams} ").format(
                    teams=humanize_list(self.source.team))
            msg = _(
                "No schedule could be found {team}in dates between {last_searched}"
            ).format(team=team, last_searched=self.source._last_searched)
            await self.message.edit(content=msg, embed=None)
            return
        self.current_page = page_number
        kwargs = await self._get_kwargs_from_page(page)
        await self.message.edit(**kwargs)

    async def send_initial_message(self, ctx, channel):
        """|coro|
        The default implementation of :meth:`Menu.send_initial_message`
        for the interactive pagination session.
        This implementation shows the first page of the source.
        """
        try:
            page = await self._source.get_page(0)
        except (IndexError, NoSchedule):

            return await channel.send(self.format_error())
        kwargs = await self._get_kwargs_from_page(page)
        return await channel.send(**kwargs)

    def format_error(self):
        team = ""
        if self.source.team:
            team = _("for {teams} ").format(
                teams=humanize_list(self.source.team))
        msg = _(
            "No schedule could be found {team}in dates between {last_searched}"
        ).format(team=team, last_searched=self.source._last_searched)
        return msg

    async def show_checked_page(self, page_number: int) -> None:
        try:
            await self.show_page(page_number)
        except IndexError:
            # An error happened that can be handled, so ignore it.
            pass

    def reaction_check(self, payload):
        """Just extends the default reaction_check to use owner_ids"""
        if payload.message_id != self.message.id:
            return False
        if payload.user_id not in (*self.bot.owner_ids, self._author_id):
            return False
        return payload.emoji in self.buttons

    def _skip_single_arrows(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages == 1

    @menus.button(
        "\N{BLACK LEFT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}",
        position=menus.First(1),
    )
    async def go_to_previous_page(self, payload):
        """go to the previous page"""
        await self.show_checked_page(self.current_page - 1)

    @menus.button(
        "\N{BLACK RIGHT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}",
        position=menus.Last(0),
    )
    async def go_to_next_page(self, payload):
        """go to the next page"""
        # log.info(f"Moving to next page, {self.current_page + 1}")
        await self.show_checked_page(self.current_page + 1)

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}",
        position=menus.First(0),
    )
    async def go_to_first_page(self, payload):
        """go to the first page"""
        await self.show_page(0, skip_prev=True)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}",
        position=menus.Last(1),
    )
    async def go_to_last_page(self, payload):
        """go to the last page"""
        # The call here is safe because it's guarded by skip_if
        await self.show_page(0, skip_next=True)

    @menus.button("\N{CROSS MARK}")
    async def stop_pages(self,
                         payload: discord.RawReactionActionEvent) -> None:
        """stops the pagination session."""
        self.stop()
        await self.message.delete()

    @menus.button("\N{TEAR-OFF CALENDAR}")
    async def choose_date(self,
                          payload: discord.RawReactionActionEvent) -> None:
        """stops the pagination session."""
        send_msg = await self.ctx.send(
            _("Enter the date you would like to see `YYYY-MM-DD` format is accepted."
              ))

        def check(m: discord.Message):
            return m.author == self.ctx.author and DATE_RE.search(
                m.clean_content)

        try:
            msg = await self.ctx.bot.wait_for("message",
                                              check=check,
                                              timeout=30)
        except asyncio.TimeoutError:
            await send_msg.delete()
            return
        search = DATE_RE.search(msg.clean_content)
        if search:
            date_str = f"{search.group(1)}-{search.group(3)}-{search.group(4)}"
            date = datetime.strptime(date_str, "%Y-%m-%d")
            # log.debug(date)
            self.source.date = date
            try:
                await self.source.prepare()
            except NoSchedule:
                return await self.ctx.send(self.format_error())
            await self.show_page(0)

    @menus.button("\N{FAMILY}")
    async def choose_teams(self,
                           payload: discord.RawReactionActionEvent) -> None:
        """stops the pagination session."""
        send_msg = await self.ctx.send(
            _("Enter the team you would like to filter for."))

        def check(m: discord.Message):
            return m.author == self.ctx.author

        try:
            msg = await self.ctx.bot.wait_for("message",
                                              check=check,
                                              timeout=30)
        except asyncio.TimeoutError:
            await send_msg.delete()
            return
        potential_teams = msg.clean_content.split()
        teams: List[str] = []
        for team, data in TEAMS.items():
            if "Team" in teams:
                continue
            nick = data["nickname"]
            short = data["tri_code"]
            pattern = fr"{short}\b|" + r"|".join(fr"\b{i}\b"
                                                 for i in team.split())
            if nick:
                pattern += r"|" + r"|".join(fr"\b{i}\b" for i in nick)
            # log.debug(pattern)
            reg: Pattern = re.compile(fr"\b{pattern}", flags=re.I)
            for pot in potential_teams:
                find = reg.findall(pot)
                if find:
                    teams.append(team)
            self.source.team = teams
        try:
            await self.source.prepare()
        except NoSchedule:
            return await self.ctx.send(self.format_error())
        await self.show_page(0)
Exemplo n.º 8
0
class GenericMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(
        self,
        source: menus.PageSource,
        cog: Optional[commands.Cog] = None,
        ctx=None,
        clear_reactions_after: bool = True,
        delete_message_after: bool = False,
        add_reactions: bool = True,
        using_custom_emoji: bool = False,
        using_embeds: bool = False,
        keyword_to_reaction_mapping: Dict[str, str] = None,
        timeout: int = 180,
        message: discord.Message = None,
        **kwargs: Any,
    ) -> None:
        self.cog = cog
        self.ctx = ctx
        super().__init__(
            source,
            clear_reactions_after=clear_reactions_after,
            delete_message_after=delete_message_after,
            check_embeds=using_embeds,
            timeout=timeout,
            message=message,
            **kwargs,
        )

    def reaction_check(self, payload):
        """The function that is used to check whether the payload should be processed.
        This is passed to :meth:`discord.ext.commands.Bot.wait_for <Bot.wait_for>`.
        There should be no reason to override this function for most users.
        Parameters
        ------------
        payload: :class:`discord.RawReactionActionEvent`
            The payload to check.
        Returns
        ---------
        :class:`bool`
            Whether the payload should be processed.
        """
        if payload.message_id != self.message.id:
            return False
        if payload.user_id not in (*self.bot.owner_ids, self._author_id):
            return False

        return payload.emoji in self.buttons

    def _skip_single_arrows(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages == 1

    def _skip_double_triangle_buttons(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages <= 2

    # left
    @menus.button("\N{BLACK LEFT-POINTING TRIANGLE}",
                  position=menus.First(1),
                  skip_if=_skip_single_arrows)
    async def prev(self, payload: discord.RawReactionActionEvent):
        if self.current_page == 0:
            await self.show_page(self._source.get_max_pages() - 1)
        else:
            await self.show_checked_page(self.current_page - 1)

    @menus.button("\N{CROSS MARK}", position=menus.First(2))
    async def stop_pages_default(
            self, payload: discord.RawReactionActionEvent) -> None:
        self.stop()
        with contextlib.suppress(discord.NotFound):
            await self.message.delete()

    @menus.button("\N{BLACK RIGHT-POINTING TRIANGLE}",
                  position=menus.First(2),
                  skip_if=_skip_single_arrows)
    async def next(self, payload: discord.RawReactionActionEvent):
        if self.current_page == self._source.get_max_pages() - 1:
            await self.show_page(0)
        else:
            await self.show_checked_page(self.current_page + 1)

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f",
        position=menus.First(0),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_first_page(self, payload: discord.RawReactionActionEvent):
        """go to the first page"""
        await self.show_page(0)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f",
        position=menus.Last(1),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_last_page(self, payload: discord.RawReactionActionEvent):
        """go to the last page"""
        # The call here is safe because it's guarded by skip_if
        await self.show_page(self._source.get_max_pages() - 1)
Exemplo n.º 9
0
class ReTriggerMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(
        self,
        source: menus.PageSource,
        cog: Optional[commands.Cog] = None,
        page_start: Optional[int] = 0,
        clear_reactions_after: bool = True,
        delete_message_after: bool = False,
        timeout: int = 60,
        message: discord.Message = None,
        **kwargs: Any,
    ) -> None:
        super().__init__(
            source,
            clear_reactions_after=clear_reactions_after,
            delete_message_after=delete_message_after,
            timeout=timeout,
            message=message,
            **kwargs,
        )
        self.cog = cog
        self.page_start = page_start

    async def send_initial_message(self, ctx, channel):
        """|coro|
        The default implementation of :meth:`Menu.send_initial_message`
        for the interactive pagination session.
        This implementation shows the first page of the source.
        """
        page = await self._source.get_page(self.page_start)
        kwargs = await self._get_kwargs_from_page(page)
        return await channel.send(**kwargs)

    async def update(self, payload):
        """|coro|

        Updates the menu after an event has been received.

        Parameters
        -----------
        payload: :class:`discord.RawReactionActionEvent`
            The reaction event that triggered this update.
        """
        button = self.buttons[payload.emoji]
        if not self._running:
            return

        try:
            if button.lock:
                async with self._lock:
                    if self._running:
                        await button(self, payload)
            else:
                await button(self, payload)
        except Exception as exc:
            log.debug("Ignored exception on reaction event", exc_info=exc)

    async def show_checked_page(self, page_number: int) -> None:
        max_pages = self._source.get_max_pages()
        try:
            if max_pages is None:
                # If it doesn't give maximum pages, it cannot be checked
                await self.show_page(page_number)
            elif page_number >= max_pages:
                await self.show_page(0)
            elif page_number < 0:
                await self.show_page(max_pages - 1)
            elif max_pages > page_number >= 0:
                await self.show_page(page_number)
        except IndexError:
            # An error happened that can be handled, so ignore it.
            pass

    def reaction_check(self, payload):
        """Just extends the default reaction_check to use owner_ids"""
        if payload.message_id != self.message.id:
            return False
        if payload.user_id not in (*self.bot.owner_ids, self._author_id):
            return False
        return payload.emoji in self.buttons

    def _skip_single_arrows(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages == 1

    def _skip_double_triangle_buttons(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages <= 2

    @menus.button(
        "\N{BLACK LEFT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}",
        position=menus.First(1),
        skip_if=_skip_single_arrows,
    )
    async def go_to_previous_page(self, payload):
        """go to the previous page"""
        await self.show_checked_page(self.current_page - 1)

    @menus.button(
        "\N{BLACK RIGHT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}",
        position=menus.Last(0),
        skip_if=_skip_single_arrows,
    )
    async def go_to_next_page(self, payload):
        """go to the next page"""
        await self.show_checked_page(self.current_page + 1)

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}",
        position=menus.First(0),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_first_page(self, payload):
        """go to the first page"""
        await self.show_page(0)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}",
        position=menus.Last(1),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_last_page(self, payload):
        """go to the last page"""
        # The call here is safe because it's guarded by skip_if
        await self.show_page(self._source.get_max_pages() - 1)

    @menus.button("\N{CROSS MARK}")
    async def stop_pages(self,
                         payload: discord.RawReactionActionEvent) -> None:
        """stops the pagination session."""
        self.stop()
        await self.message.delete()

    @menus.button(
        "\N{BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR}\N{VARIATION SELECTOR-16}"
    )
    async def toggle_trigger(self,
                             payload: discord.RawReactionActionEvent) -> None:
        """Enables and disables triggers"""
        member = self.ctx.guild.get_member(payload.user_id)
        if await self.cog.can_edit(member, self.source.selection):
            self.source.selection.toggle()
            await self.show_checked_page(self.current_page)

    @menus.button("\N{NEGATIVE SQUARED CROSS MARK}")
    async def stop_trigger(self,
                           payload: discord.RawReactionActionEvent) -> None:
        """Enables and disables triggers"""
        member = self.ctx.guild.get_member(payload.user_id)
        if await self.cog.can_edit(member, self.source.selection):
            self.source.selection.disable()
            await self.show_checked_page(self.current_page)

    @menus.button("\N{WHITE HEAVY CHECK MARK}")
    async def enable_trigger(self,
                             payload: discord.RawReactionActionEvent) -> None:
        """Enables and disables triggers"""
        member = self.ctx.guild.get_member(payload.user_id)
        if await self.cog.can_edit(member, self.source.selection):
            self.source.selection.enable()
            await self.show_checked_page(self.current_page)

    @menus.button("\N{PUT LITTER IN ITS PLACE SYMBOL}")
    async def delete_trigger(self,
                             payload: discord.RawReactionActionEvent) -> None:
        """Enables and disables triggers"""
        member = self.ctx.guild.get_member(payload.user_id)
        if await self.cog.can_edit(member, self.source.selection):
            msg = await self.ctx.send(
                _("Are you sure you want to delete trigger {name}?").format(
                    name=self.source.selection.name))
            start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS)
            pred = ReactionPredicate.yes_or_no(msg, self.ctx.author)
            await self.ctx.bot.wait_for("reaction_add", check=pred)
            if pred.result:
                await msg.delete()
                self.source.selection.disable()
                done = await self.cog.remove_trigger(
                    payload.guild_id, self.source.selection.name)
                if done:
                    page = await self._source.get_page(self.current_page)
                    kwargs = await self._get_kwargs_from_page(page)
                    await self.message.edit(
                        content=_("This trigger has been deleted."),
                        embed=kwargs["embed"])
                    for t in self.cog.triggers[self.ctx.guild.id]:
                        if t.name == self.source.selection.name:
                            self.cog.triggers[self.ctx.guild.id].remove(t)
Exemplo n.º 10
0
class PokeListMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(
        self,
        source: menus.PageSource,
        cog: Optional[commands.Cog] = None,
        ctx=None,
        user=None,
        clear_reactions_after: bool = True,
        delete_message_after: bool = False,
        add_reactions: bool = True,
        using_custom_emoji: bool = False,
        using_embeds: bool = False,
        keyword_to_reaction_mapping: Dict[str, str] = None,
        timeout: int = 180,
        message: discord.Message = None,
        **kwargs: Any,
    ) -> None:
        self.cog = cog
        self.ctx = ctx
        self.user = user
        self._search_lock = asyncio.Lock()
        self._search_task: asyncio.Task = None
        super().__init__(
            source,
            clear_reactions_after=clear_reactions_after,
            delete_message_after=delete_message_after,
            check_embeds=using_embeds,
            timeout=timeout,
            message=message,
            **kwargs,
        )

    async def finalize(self, timed_out):
        if not self._running:
            return

        await self.stop(do_super=False)

    async def stop(self, do_super: bool = True):
        if self._search_task is not None:
            self._search_task.cancel()
        if do_super:
            super().stop()

    async def _number_page_task(self):
        async def cleanup(messages: List[discord.Message]):
            with contextlib.suppress(discord.HTTPException):
                for msg in messages:
                    await msg.delete()

        async with self._search_lock:
            prompt = await self.ctx.send(
                _("Please select the Pokémon ID number to jump to."))
            try:
                pred = MessagePredicate.valid_int(self.ctx)
                msg = await self.bot.wait_for("message_without_command",
                                              check=pred,
                                              timeout=10.0)
                jump_page = int(msg.content)
                if jump_page > self._source.get_max_pages():
                    await self.ctx.send(
                        _("Invalid Pokémon ID, jumping to the end."),
                        delete_after=5)
                    jump_page = self._source.get_max_pages()
                await self.show_checked_page(jump_page - 1)
                await cleanup([prompt, msg])
            except (ValueError, asyncio.TimeoutError, asyncio.CancelledError):
                await cleanup([prompt])

            self._search_task = None

    def reaction_check(self, payload):
        """The function that is used to check whether the payload should be processed.
        This is passed to :meth:`discord.ext.commands.Bot.wait_for <Bot.wait_for>`.

        There should be no reason to override this function for most users.

        Parameters
        ------------
        payload: :class:`discord.RawReactionActionEvent`
            The payload to check.

        Returns
        ---------
        :class:`bool`
            Whether the payload should be processed.
        """
        if payload.message_id != self.message.id:
            return False
        if payload.user_id not in (*self.bot.owner_ids, self._author_id):
            return False

        return payload.emoji in self.buttons

    def _cant_select(self):
        return self.ctx.author != self.user

    @menus.button("\N{BLACK LEFT-POINTING TRIANGLE}", position=menus.First(0))
    async def prev(self, payload: discord.RawReactionActionEvent):
        if self.current_page == 0:
            await self.show_page(self._source.get_max_pages() - 1)
        else:
            await self.show_checked_page(self.current_page - 1)

    @menus.button("\N{CROSS MARK}", position=menus.First(1))
    async def stop_pages_default(
            self, payload: discord.RawReactionActionEvent) -> None:
        with contextlib.suppress(discord.NotFound):
            await self.message.delete()

        await self.stop()

    @menus.button("\N{BLACK RIGHT-POINTING TRIANGLE}", position=menus.First(2))
    async def next(self, payload: discord.RawReactionActionEvent):
        if self.current_page == self._source.get_max_pages() - 1:
            await self.show_page(0)
        else:
            await self.show_checked_page(self.current_page + 1)

    @menus.button("\N{LEFT-POINTING MAGNIFYING GLASS}",
                  position=menus.First(4))
    async def number_page(self, payload: discord.RawReactionActionEvent):
        if self._search_lock.locked() and self._search_task is not None:
            return

        self._search_task = asyncio.get_running_loop().create_task(
            self._number_page_task())

    @menus.button("\N{WHITE HEAVY CHECK MARK}",
                  position=menus.First(3),
                  skip_if=_cant_select)
    async def select(self, payload: discord.RawReactionActionEvent):
        command = self.ctx.bot.get_command("select")
        await self.ctx.invoke(command, _id=self.current_page + 1)
Exemplo n.º 11
0
class Menu(menus.MenuPages, inherit_buttons=False):
    def __init__(self, source: Page):
        super().__init__(source)

    async def show_checked_page(self, page_number: int):
        max_pages = self.source.get_max_pages()
        try:
            if max_pages is None or max_pages > page_number >= 0:
                await self.show_page(page_number)
            elif max_pages <= page_number:
                await self.show_page(0)
            elif 0 > page_number:
                await self.show_page(max_pages - 1)
        except IndexError:
            pass

    async def send_initial_message(
            self, ctx: commands.Context,
            channel: discord.TextChannel) -> discord.Message:
        page = await self.source.get_page(0)
        self.current_page = 0
        kwargs = await self._get_kwargs_from_page(page)
        return await ctx.send(**kwargs)

    def _skip_double_triangle_buttons(self) -> bool:
        max_pages = self.source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages < 5

    def _skip_single_triangle_buttons(self) -> bool:
        max_pages = self.source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages == 1

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE}\N{VARIATION SELECTOR-16}",
        position=menus.First(0),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_first_page(self, payload):
        await self.show_page(0)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE}\N{VARIATION SELECTOR-16}",
        position=menus.Last(1),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_last_page(self, payload):
        await self.show_page(self.source.get_max_pages() - 1)

    @menus.button(
        "\N{LEFTWARDS BLACK ARROW}\N{VARIATION SELECTOR-16}",
        position=menus.First(1),
        skip_if=_skip_single_triangle_buttons,
    )
    async def go_to_previous_page(self, payload):
        await self.show_checked_page(self.current_page - 1)

    @menus.button(
        "\N{BLACK RIGHTWARDS ARROW}\N{VARIATION SELECTOR-16}",
        position=menus.Last(0),
        skip_if=_skip_single_triangle_buttons,
    )
    async def go_to_next_page(self, payload):
        await self.show_checked_page(self.current_page + 1)

    @menus.button("\N{CROSS MARK}")
    async def stop_pages(self, payload):
        self.stop()
        with suppress(discord.Forbidden):
            await self.message.delete()
Exemplo n.º 12
0
                                   channel: discord.abc.Messageable,
                                   page: int = 0):
        """

        The default implementation of :meth:`Menu.send_initial_message`
        for the interactive pagination session.

        This implementation shows the first page of the source.
        """
        page = await self._source.get_page(page)
        kwargs = await self._get_kwargs_from_page(page)
        return await channel.send(**kwargs)

    @_dpy_menus.button(
        "\N{BLACK LEFT-POINTING TRIANGLE}\ufe0f",
        position=_dpy_menus.First(1),
        skip_if=_skip_single_arrows,
    )
    async def go_to_previous_page(self, payload):
        """go to the previous page"""
        await self.show_checked_page(self.current_page - 1)

    @_dpy_menus.button(
        "\N{BLACK RIGHT-POINTING TRIANGLE}\ufe0f",
        position=_dpy_menus.Last(0),
        skip_if=_skip_single_arrows,
    )
    async def go_to_next_page(self, payload):
        """go to the next page"""
        await self.show_checked_page(self.current_page + 1)
Exemplo n.º 13
0
class TopMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(
        self,
        source: menus.PageSource,
        timeout: int = 30,
    ):
        super().__init__(
            source,
            timeout=timeout,
            clear_reactions_after=True,
            delete_message_after=True,
        )

    def _skip_double_triangle_buttons(self):
        return super()._skip_double_triangle_buttons()

    async def finalize(self, timed_out):
        """|coro|
        A coroutine that is called when the menu loop has completed
        its run. This is useful if some asynchronous clean-up is
        required after the fact.
        Parameters
        --------------
        timed_out: :class:`bool`
            Whether the menu completed due to timing out.
        """
        if timed_out and self.delete_message_after:
            self.delete_message_after = False

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f",
        position=menus.First(0),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_first_page(self, payload):
        """go to the first page"""
        await self.show_page(0)

    @menus.button("\N{BLACK LEFT-POINTING TRIANGLE}\ufe0f",
                  position=menus.First(1))
    async def go_to_previous_page(self, payload):
        """go to the previous page"""
        if self.current_page == 0:
            await self.show_page(self._source.get_max_pages() - 1)
        else:
            await self.show_checked_page(self.current_page - 1)

    @menus.button("\N{BLACK RIGHT-POINTING TRIANGLE}\ufe0f",
                  position=menus.Last(0))
    async def go_to_next_page(self, payload):
        """go to the next page"""
        if self.current_page == self._source.get_max_pages() - 1:
            await self.show_page(0)
        else:
            await self.show_checked_page(self.current_page + 1)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f",
        position=menus.Last(1),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_last_page(self, payload):
        """go to the last page"""
        # The call here is safe because it's guarded by skip_if
        await self.show_page(self._source.get_max_pages() - 1)

    @menus.button("\N{NUMBER SIGN}\ufe0f\N{COMBINING ENCLOSING KEYCAP}",
                  position=menus.Last(2))
    async def number_page(self, payload):
        prompt = await self.ctx.send(
            "Send a number of page that you wish to see")
        try:
            pred = MessagePredicate.positive(self.ctx)
            msg = await self.bot.wait_for(
                "message_without_command",
                check=pred,
                timeout=10.0,
            )
            if pred.result:
                jump_page = int(msg.content)
                if jump_page > self._source.get_max_pages():
                    jump_page = self._source.get_max_pages()
                await self.show_checked_page(jump_page - 1)
                if self.ctx.channel.permissions_for(
                        self.ctx.me).manage_messages:
                    with suppress(discord.HTTPException):
                        await msg.delete()
        except asyncio.TimeoutError:
            pass
        finally:
            with suppress(discord.HTTPException):
                await prompt.delete()

    @menus.button("\N{CROSS MARK}", position=menus.First(2))
    async def stop_pages(self,
                         payload: discord.RawReactionActionEvent) -> None:
        self.stop()
Exemplo n.º 14
0
class ClientMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(
        self,
        source: menus.PageSource,
        cog: Optional[commands.Cog] = None,
        ctx=None,
        user=None,
        clear_reactions_after: bool = True,
        delete_message_after: bool = True,
        add_reactions: bool = True,
        using_custom_emoji: bool = False,
        using_embeds: bool = False,
        keyword_to_reaction_mapping: Dict[str, str] = None,
        timeout: int = 180,
        message: discord.Message = None,
        **kwargs: Any,
    ) -> None:
        super().__init__(
            source,
            clear_reactions_after=clear_reactions_after,
            delete_message_after=delete_message_after,
            check_embeds=using_embeds,
            timeout=timeout,
            message=message,
            **kwargs,
        )

    def reaction_check(self, payload):
        """The function that is used to check whether the payload should be processed.
        This is passed to :meth:`discord.ext.commands.Bot.wait_for <Bot.wait_for>`.

        There should be no reason to override this function for most users.

        Parameters
        ------------
        payload: :class:`discord.RawReactionActionEvent`
            The payload to check.

        Returns
        ---------
        :class:`bool`
            Whether the payload should be processed.
        """
        if payload.message_id != self.message.id:
            return False
        if payload.user_id not in (*self.bot.owner_ids, self._author_id):
            return False

        return payload.emoji in self.buttons

    @menus.button("\N{BLACK LEFT-POINTING TRIANGLE}", position=menus.First(0))
    async def prev(self, payload: discord.RawReactionActionEvent):
        if self.current_page == 0:
            await self.show_page(self._source.get_max_pages() - 1)
        else:
            await self.show_checked_page(self.current_page - 1)

    @menus.button("\N{CROSS MARK}", position=menus.First(1))
    async def stop_pages_default(
            self, payload: discord.RawReactionActionEvent) -> None:
        self.stop()

    @menus.button("\N{BLACK RIGHT-POINTING TRIANGLE}", position=menus.First(2))
    async def next(self, payload: discord.RawReactionActionEvent):
        if self.current_page == self._source.get_max_pages() - 1:
            await self.show_page(0)
        else:
            await self.show_checked_page(self.current_page + 1)

    @menus.button("\N{WARNING SIGN}\N{VARIATION SELECTOR-16}",
                  position=menus.First(3))
    async def close_ws(self, payload: discord.RawReactionActionEvent):
        number = self.current_page
        msg = await self.ctx.send((
            f"Are you sure you want to close RPC Client {number + 1}?  "
            "This will prevent them from communicating and may raise errors if not handled properly."
        ))
        emojis = ReactionPredicate.YES_OR_NO_EMOJIS
        start_adding_reactions(msg, emojis)
        pred = ReactionPredicate.yes_or_no(msg, self.ctx.author)
        with contextlib.suppress(asyncio.TimeoutError):
            await self.ctx.bot.wait_for("reaction_add", check=pred, timeout=30)

        if pred.result:
            # Definitely do NOT do this at home
            self.ctx.bot.rpc._rpc.clients[number].ws._reader.set_exception(
                Exception)
            await self.ctx.send(f"Successfully closed RPC Client {number + 1}")
        else:
            await self.ctx.send("Cancelled.")
Exemplo n.º 15
0
class BaseMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(
        self,
        source: menus.PageSource,
        cog: commands.Cog,
        clear_reactions_after: bool = True,
        delete_message_after: bool = False,
        timeout: int = 60,
        message: discord.Message = None,
        page_start: int = 0,
        **kwargs: Any,
    ) -> None:
        super().__init__(
            source,
            clear_reactions_after=clear_reactions_after,
            delete_message_after=delete_message_after,
            timeout=timeout,
            message=message,
            **kwargs,
        )
        self.cog = cog
        self.page_start = page_start

    async def send_initial_message(self, ctx, channel):
        """|coro|
        The default implementation of :meth:`Menu.send_initial_message`
        for the interactive pagination session.
        This implementation shows the first page of the source.
        """
        page = await self._source.get_page(self.page_start)
        kwargs = await self._get_kwargs_from_page(page)
        return await channel.send(**kwargs)

    async def show_checked_page(self, page_number: int) -> None:
        max_pages = self._source.get_max_pages()
        try:
            if max_pages is None:
                # If it doesn't give maximum pages, it cannot be checked
                await self.show_page(page_number)
            elif page_number >= max_pages:
                await self.show_page(0)
            elif page_number < 0:
                await self.show_page(max_pages - 1)
            elif max_pages > page_number >= 0:
                await self.show_page(page_number)
        except IndexError:
            # An error happened that can be handled, so ignore it.
            pass

    def reaction_check(self, payload):
        """Just extends the default reaction_check to use owner_ids"""
        if payload.message_id != self.message.id:
            return False
        if payload.user_id not in (*self.bot.owner_ids, self._author_id):
            return False
        return payload.emoji in self.buttons

    def _skip_single_arrows(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages == 1

    def _skip_double_triangle_buttons(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages <= 2

    def _skip_non_guild_buttons(self) -> bool:
        if self.ctx.author.id not in self.bot.owner_ids:
            return True
        if isinstance(self.source, GuildPages):
            return False
        return True

    @menus.button(
        "\N{BLACK LEFT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}",
        position=menus.First(1),
        skip_if=_skip_single_arrows,
    )
    async def go_to_previous_page(self, payload):
        """go to the previous page"""
        await self.show_checked_page(self.current_page - 1)

    @menus.button(
        "\N{BLACK RIGHT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}",
        position=menus.Last(0),
        skip_if=_skip_single_arrows,
    )
    async def go_to_next_page(self, payload):
        """go to the next page"""
        await self.show_checked_page(self.current_page + 1)

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}",
        position=menus.First(0),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_first_page(self, payload):
        """go to the first page"""
        await self.show_page(0)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}",
        position=menus.Last(1),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_last_page(self, payload):
        """go to the last page"""
        # The call here is safe because it's guarded by skip_if
        await self.show_page(self._source.get_max_pages() - 1)

    @menus.button("\N{CROSS MARK}")
    async def stop_pages(self,
                         payload: discord.RawReactionActionEvent) -> None:
        """stops the pagination session."""
        self.stop()
        await self.message.delete()

    @menus.button("\N{OUTBOX TRAY}", skip_if=_skip_non_guild_buttons)
    async def leave_guild_button(self, payload):
        await self.cog.confirm_leave_guild(self.ctx, self.source.guild)

    @menus.button("\N{INBOX TRAY}", skip_if=_skip_non_guild_buttons)
    async def make_guild_invite_button(self, payload):
        invite = await self.cog.get_guild_invite(self.source.guild)
        if invite:
            await self.ctx.send(str(invite))
        else:
            await self.ctx.send(
                _("I cannot find or create an invite for `{guild}`").format(
                    guild=self.source.guild.name))
Exemplo n.º 16
0
class MjolnirMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(self,
                 source: menus.ListPageSource,
                 page_start: int = 0,
                 **kwargs):
        self.page_start = page_start
        super().__init__(source=source, **kwargs)

    async def send_initial_message(self, ctx, channel):
        self.current_page = self.page_start
        page = await self._source.get_page(self.page_start)
        kwargs = await self._get_kwargs_from_page(page)
        await channel.send(**kwargs)

    async def show_checked_page(self, page_number: int):
        max_pages = self._source.get_max_pages()
        if max_pages is None or max_pages > page_number >= 0:
            await self.show_page(page_number)
        elif page_number > max_pages:
            await self.show_page(0)
        elif page_number < 0:
            await self.show_page(max_pages - 1)

    def _skip_single_arrows(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages == 1

    def _skip_double_triangle_buttons(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages <= 4

    @menus.button("\N{BLACK LEFT-POINTING DOUBLE TRIANGLE}",
                  position=menus.First(0),
                  skip_if=_skip_double_triangle_buttons)
    async def go_to_first_page(self, payload):
        await self.show_checked_page(0)

    @menus.button("\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE}",
                  position=menus.Last(1),
                  skip_if=_skip_double_triangle_buttons)
    async def go_to_last_page(self, payload):
        await self.show_checked_page(self._source.get_max_pages() - 1)

    @menus.button("\N{LEFTWARDS BLACK ARROW}",
                  position=menus.First(1),
                  skip_if=_skip_single_arrows)
    async def go_to_previous_page(self, payload):
        await self.show_checked_page(self.current_page - 1)

    @menus.button("\N{BLACK RIGHTWARDS ARROW}",
                  position=menus.Last(0),
                  skip_if=_skip_single_arrows)
    async def go_to_next_page(self, payload):
        await self.show_checked_page(self.current_page + 1)

    @menus.button("\N{CROSS MARK}")
    async def stop_pages(self, payload):
        self.stop()
        await self.message.delete()
Exemplo n.º 17
0
class ChannelsMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(self, sources: dict, channel_type: str, total_channels: int, timeout: int = 30):
        super().__init__(
            sources[next(iter(sources))],
            timeout=timeout,
            clear_reactions_after=True,
            delete_message_after=True,
        )
        self.sources = sources
        self.channel_type = channel_type
        self.total_channels = total_channels

    async def set_source(self, channel_type):
        self.channel_type = channel_type
        await self.change_source(self.sources[channel_type])

    def should_add_reactions(self):
        return True

    @menus.button("\N{BOOKMARK TABS}", position=menus.First(0), skip_if=check_channels("category"))
    async def switch_category(self, payload):
        await self.set_source("category")

    @menus.button("\N{SPEECH BALLOON}", position=menus.First(1), skip_if=check_channels("text"))
    async def switch_text(self, payload):
        await self.set_source("text")

    @menus.button("\N{SPEAKER}", position=menus.First(2), skip_if=check_channels("voice"))
    async def switch_voice(self, payload):
        await self.set_source("voice")

    @menus.button(
        "\N{SATELLITE ANTENNA}",
        position=menus.First(3),
        skip_if=check_channels("stage"),
    )
    async def switch_stage(self, payload):
        await self.set_source("stage")

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f",
        position=menus.First(3),
    )
    async def go_to_first_page(self, payload):
        """go to the first page"""
        await self.show_page(0)

    @menus.button("\N{BLACK LEFT-POINTING TRIANGLE}\ufe0f", position=menus.First(4))
    async def go_to_previous_page(self, payload):
        """go to the previous page"""
        if self.current_page == 0:
            await self.show_page(self._source.get_max_pages() - 1)
        else:
            await self.show_checked_page(self.current_page - 1)

    @menus.button("\N{BLACK RIGHT-POINTING TRIANGLE}\ufe0f", position=menus.Last(0))
    async def go_to_next_page(self, payload):
        """go to the next page"""
        if self.current_page == self._source.get_max_pages() - 1:
            await self.show_page(0)
        else:
            await self.show_checked_page(self.current_page + 1)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f",
        position=menus.Last(1),
    )
    async def go_to_last_page(self, payload):
        """go to the last page"""
        # The call here is safe because it's guarded by skip_if
        await self.show_page(self._source.get_max_pages() - 1)

    @menus.button("\N{CROSS MARK}", position=menus.First(5))
    async def stop_pages(self, payload: discord.RawReactionActionEvent) -> None:
        self.stop()
Exemplo n.º 18
0
class JojoMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(self,
                 source: menus.ListPageSource,
                 timeout: int = 30,
                 delete_message_after: bool = False,
                 clear_reactions_after: bool = True,
                 message: discord.Message = None,
                 page_start: int = 0,
                 **kwargs: typing.Any):
        super().__init__(source=source,
                         delete_message_after=delete_message_after,
                         timeout=timeout,
                         clear_reactions_after=clear_reactions_after,
                         message=message,
                         **kwargs)
        self.page_start = page_start

    async def send_initial_message(self, ctx: commands.Context,
                                   channel: discord.TextChannel):
        self.current_page = self.page_start
        page = await self._source.get_page(self.page_start)
        kwargs = await self._get_kwargs_from_page(page)
        return await channel.send(**kwargs)

    async def show_checked_page(self, page_number: int):
        max_pages = self._source.get_max_pages()
        if max_pages is None or max_pages > page_number >= 0:
            await self.show_page(page_number)
        elif page_number >= max_pages:
            await self.show_page(0)
        elif page_number < 0:
            await self.show_page(max_pages - 1)

    def _skip_single_arrows(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages == 1

    def _skip_double_triangle_buttons(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages <= 4

    @menus.button("\N{BLACK LEFT-POINTING DOUBLE TRIANGLE}",
                  position=menus.First(0),
                  skip_if=_skip_double_triangle_buttons)
    async def go_to_first_page(self, payload):
        await self.show_checked_page(0)

    @menus.button("\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE}",
                  position=menus.Last(1),
                  skip_if=_skip_double_triangle_buttons)
    async def go_to_last_page(self, payload):
        await self.show_checked_page(page_number=self._source.max_pages() - 1)

    @menus.button("\N{LEFTWARDS BLACK ARROW}",
                  position=menus.First(1),
                  skip_if=_skip_single_arrows)
    async def go_to_previous_page(self, payload):
        await self.show_checked_page(self.current_page - 1)

    @menus.button("\N{BLACK RIGHTWARDS ARROW}",
                  position=menus.Last(0),
                  skip_if=_skip_single_arrows)
    async def go_to_next_page(self, payload):
        await self.show_checked_page(self.current_page + 1)

    @menus.button("\N{CROSS MARK}")
    async def stop_pages(self, payload):
        self.stop()
        await self.message.delete()
Exemplo n.º 19
0
class WSStatsMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(
        self,
        source: menus.PageSource,
        header: str,
        timeout: int = 30,
        image: BytesIO = None,
    ):
        super().__init__(
            source,
            timeout=timeout,
            clear_reactions_after=True,
            delete_message_after=True,
        )
        self.header = header
        self.image = image

    def should_add_reactions(self):
        return True

    def not_paginating(self):
        return not self._source.is_paginating()

    async def send_initial_message(self, ctx, channel):
        page = await self._source.get_page(0)
        kwargs = await self._get_kwargs_from_page(page)
        msg = await channel.send(
            **kwargs,
            file=discord.File(self.image, filename="chart.png")
            if self.image else None,
        )
        if self.image:
            self.image.close()
        return msg

    async def finalize(self, timed_out):
        """|coro|
        A coroutine that is called when the menu loop has completed
        its run. This is useful if some asynchronous clean-up is
        required after the fact.
        Parameters
        --------------
        timed_out: :class:`bool`
            Whether the menu completed due to timing out.
        """
        if timed_out and self.delete_message_after:
            self.delete_message_after = False

    async def go_to_first_page(self, payload):
        """go to the first page"""
        await self.show_page(0)

    @menus.button(
        "\N{BLACK LEFT-POINTING TRIANGLE}\ufe0f",
        position=menus.First(1),
        skip_if=not_paginating,
    )
    async def go_to_previous_page(self, payload):
        """go to the previous page"""
        await self.show_checked_page(self.current_page - 1)

    @menus.button(
        "\N{BLACK RIGHT-POINTING TRIANGLE}\ufe0f",
        position=menus.Last(0),
        skip_if=not_paginating,
    )
    async def go_to_next_page(self, payload):
        """go to the next page"""
        await self.show_checked_page(self.current_page + 1)

    @menus.button("\N{CROSS MARK}", position=menus.First(2))
    async def stop_pages(self,
                         payload: discord.RawReactionActionEvent) -> None:
        self.stop()
Exemplo n.º 20
0
class BadgeMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(
        self,
        source: menus.PageSource,
        timeout: int = 30,
        can_buy=False,
    ):
        super().__init__(
            source,
            timeout=timeout,
            clear_reactions_after=True,
            delete_message_after=True,
        )
        self.can_buy = can_buy

    async def start(self, ctx, *, channel=None, wait=False):
        if self.can_buy:
            self.can_buy = await ctx.cog.buy_badge.can_run(
                ctx, check_all_parents=True)
        await super().start(ctx, channel=channel, wait=wait)

    def should_add_reactions(self):
        return True

    def _no_pages(self):
        return not self._source.is_paginating()

    def _skip_double_triangle_buttons(self):
        return (not self._source.is_paginating()
                ) or super()._skip_double_triangle_buttons()

    async def finalize(self, timed_out):
        """|coro|
        A coroutine that is called when the menu loop has completed
        its run. This is useful if some asynchronous clean-up is
        required after the fact.
        Parameters
        --------------
        timed_out: :class:`bool`
            Whether the menu completed due to timing out.
        """
        if timed_out and self.delete_message_after:
            self.delete_message_after = False

    def cant_buy_check(self):
        return not self.can_buy

    @menus.button("\N{BANKNOTE WITH DOLLAR SIGN}",
                  position=menus.First(0),
                  skip_if=cant_buy_check)
    async def buy_badge(self, payload):
        page = await self.source.get_page(self.current_page)
        await self.ctx.invoke(
            self.ctx.cog.buy_badge,
            is_global=True if page["server_id"] == "global" else False,
            name=page["badge_name"],
        )

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f",
        position=menus.First(0),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_first_page(self, payload):
        """go to the first page"""
        await self.show_page(0)

    @menus.button("\N{BLACK LEFT-POINTING TRIANGLE}\ufe0f",
                  position=menus.First(1),
                  skip_if=_no_pages)
    async def go_to_previous_page(self, payload):
        """go to the previous page"""
        if self.current_page == 0:
            await self.show_page(self._source.get_max_pages() - 1)
        else:
            await self.show_checked_page(self.current_page - 1)

    @menus.button("\N{BLACK RIGHT-POINTING TRIANGLE}\ufe0f",
                  position=menus.Last(0),
                  skip_if=_no_pages)
    async def go_to_next_page(self, payload):
        """go to the next page"""
        if self.current_page == self._source.get_max_pages() - 1:
            await self.show_page(0)
        else:
            await self.show_checked_page(self.current_page + 1)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f",
        position=menus.Last(1),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_last_page(self, payload):
        """go to the last page"""
        # The call here is safe because it's guarded by skip_if
        await self.show_page(self._source.get_max_pages() - 1)

    @menus.button("\N{CROSS MARK}", position=menus.First(2))
    async def stop_pages(self,
                         payload: discord.RawReactionActionEvent) -> None:
        self.stop()
Exemplo n.º 21
0
class BackgroundMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(
        self,
        sources: dict,
        bg_type: str,
        timeout: int = 30,
    ):
        super().__init__(
            sources[bg_type],
            timeout=timeout,
            clear_reactions_after=True,
            delete_message_after=True,
        )
        self.sources = sources
        self.bg_type = bg_type

    async def finalize(self, timed_out):
        """|coro|
        A coroutine that is called when the menu loop has completed
        its run. This is useful if some asynchronous clean-up is
        required after the fact.
        Parameters
        --------------
        timed_out: :class:`bool`
            Whether the menu completed due to timing out.
        """
        if timed_out and self.delete_message_after:
            self.delete_message_after = False

    def should_add_reactions(self):
        return True

    async def set_source(self, bg_type):
        self.bg_type = bg_type
        await self.change_source(self.sources[bg_type])

    @menus.button("\N{RECEIPT}", position=menus.First(0))
    async def switch_profile(self, payload):
        await self.set_source("profile")

    @menus.button("\N{CARD INDEX}", position=menus.First(1))
    async def switch_rank(self, payload):
        await self.set_source("rank")

    @menus.button("\N{SQUARED UP WITH EXCLAMATION MARK}",
                  position=menus.First(2))
    async def switch_levelup(self, payload):
        await self.set_source("levelup")

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f",
        position=menus.First(3),
    )
    async def go_to_first_page(self, payload):
        """go to the first page"""
        await self.show_page(0)

    @menus.button("\N{BLACK LEFT-POINTING TRIANGLE}\ufe0f",
                  position=menus.First(4))
    async def go_to_previous_page(self, payload):
        """go to the previous page"""
        if self.current_page == 0:
            await self.show_page(self._source.get_max_pages() - 1)
        else:
            await self.show_checked_page(self.current_page - 1)

    @menus.button("\N{BLACK RIGHT-POINTING TRIANGLE}\ufe0f",
                  position=menus.Last(0))
    async def go_to_next_page(self, payload):
        """go to the next page"""
        if self.current_page == self._source.get_max_pages() - 1:
            await self.show_page(0)
        else:
            await self.show_checked_page(self.current_page + 1)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f",
        position=menus.Last(1),
    )
    async def go_to_last_page(self, payload):
        """go to the last page"""
        # The call here is safe because it's guarded by skip_if
        await self.show_page(self._source.get_max_pages() - 1)

    @menus.button("\N{CROSS MARK}", position=menus.First(5))
    async def stop_pages(self,
                         payload: discord.RawReactionActionEvent) -> None:
        self.stop()
Exemplo n.º 22
0
class Menu(menus.MenuPages, inherit_buttons=False):  # type:ignore
    message: discord.Message

    def __init__(self, source: Pages):
        super().__init__(
            source,
            timeout=30.0,
            delete_message_after=False,
            clear_reactions_after=True,
            message=None,
        )

    @property
    def source(self) -> Pages:
        return self._source

    async def send_initial_message(self, ctx, channel) -> discord.Message:
        page = await self.source.get_page(0)
        kwargs = await self._get_kwargs_from_page(page)
        return await channel.send(**kwargs)

    async def show_checked_page(self, page_number: int) -> None:
        max_pages = self.source.get_max_pages()
        try:
            if max_pages is None or max_pages > page_number >= 0:
                await self.show_page(page_number)
            elif page_number >= max_pages:
                await self.show_page(0)
            elif page_number < 0:
                await self.show_page(max_pages - 1)
        except IndexError:
            pass

    def _skip_triangle_buttons(self) -> bool:
        max_pages = self.source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages == 1

    def _skip_double_triangle_buttons(self) -> bool:
        max_pages = self.source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages <= 5

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE}",
        position=menus.First(0),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_first_page(self, payload):
        await self.show_page(0)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE}",
        position=menus.Last(1),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_last_page(self, payload):
        await self.show_page(self.source.get_max_pages() - 1)

    @menus.button(
        "\N{LEFTWARDS BLACK ARROW}\N{VARIATION SELECTOR-16}",
        position=menus.First(1),
        skip_if=_skip_triangle_buttons,
    )
    async def go_to_previous_page(self, payload):
        await self.show_checked_page(self.current_page - 1)

    @menus.button(
        "\N{BLACK RIGHTWARDS ARROW}\N{VARIATION SELECTOR-16}",
        position=menus.Last(0),
        skip_if=_skip_triangle_buttons,
    )
    async def go_to_next_page(self, payload):
        await self.show_checked_page(self.current_page + 1)

    @menus.button("\N{CROSS MARK}")
    async def stop_pages(self, payload):
        self.stop()
        with contextlib.suppress(discord.Forbidden):
            await self.message.delete()
Exemplo n.º 23
0
class ResultMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(self, **kwargs):
        super().__init__(
            **kwargs,
            timeout=60,
            clear_reactions_after=True,
            delete_message_after=True,
        )

    def _skip_double_triangle_buttons(self):
        return super()._skip_double_triangle_buttons()

    async def finalize(self, timed_out):
        """|coro|
        A coroutine that is called when the menu loop has completed
        its run. This is useful if some asynchronous clean-up is
        required after the fact.
        Parameters
        --------------
        timed_out: :class:`bool`
            Whether the menu completed due to timing out.
        """
        if timed_out and self.delete_message_after:
            self.delete_message_after = False

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f",
        position=menus.First(0),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_first_page(self, payload):
        """go to the first page"""
        await self.show_page(0)

    @menus.button("\N{BLACK LEFT-POINTING TRIANGLE}\ufe0f",
                  position=menus.First(1))
    async def go_to_previous_page(self, payload):
        """go to the previous page"""
        if self.current_page == 0:
            await self.show_page(self._source.get_max_pages() - 1)
        else:
            await self.show_checked_page(self.current_page - 1)

    @menus.button("\N{BLACK RIGHT-POINTING TRIANGLE}\ufe0f",
                  position=menus.Last(0))
    async def go_to_next_page(self, payload):
        """go to the next page"""
        if self.current_page == self._source.get_max_pages() - 1:
            await self.show_page(0)
        else:
            await self.show_checked_page(self.current_page + 1)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f",
        position=menus.Last(1),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_last_page(self, payload):
        """go to the last page"""
        # The call here is safe because it's guarded by skip_if
        await self.show_page(self._source.get_max_pages() - 1)

    @menus.button("\N{CROSS MARK}", position=menus.First(2))
    async def stop_pages(self, payload) -> None:
        self.stop()
Exemplo n.º 24
0
class BaseMenu(menus.MenuPages, inherit_buttons=False):
    def __init__(
        self,
        source: menus.PageSource,
        cog: Optional[commands.Cog] = None,
        page_start: Optional[int] = 0,
        clear_reactions_after: bool = True,
        delete_message_after: bool = False,
        timeout: int = 60,
        message: discord.Message = None,
        **kwargs: Any,
    ) -> None:
        super().__init__(
            source,
            clear_reactions_after=clear_reactions_after,
            delete_message_after=delete_message_after,
            timeout=timeout,
            message=message,
            **kwargs,
        )
        self.cog = cog
        self.page_start = page_start

    async def send_initial_message(self, ctx, channel):
        """|coro|
        The default implementation of :meth:`Menu.send_initial_message`
        for the interactive pagination session.
        This implementation shows the first page of the source.
        """
        self.current_page = self.page_start
        page = await self._source.get_page(self.page_start)
        kwargs = await self._get_kwargs_from_page(page)
        return await channel.send(**kwargs)

    async def update(self, payload):
        """|coro|

        Updates the menu after an event has been received.

        Parameters
        -----------
        payload: :class:`discord.RawReactionActionEvent`
            The reaction event that triggered this update.
        """
        button = self.buttons[payload.emoji]
        if not self._running:
            return

        try:
            if button.lock:
                async with self._lock:
                    if self._running:
                        await button(self, payload)
            else:
                await button(self, payload)
        except Exception as exc:
            log.debug("Ignored exception on reaction event", exc_info=exc)

    async def show_checked_page(self, page_number: int) -> None:
        max_pages = self._source.get_max_pages()
        try:
            if max_pages is None:
                # If it doesn't give maximum pages, it cannot be checked
                await self.show_page(page_number)
            elif page_number >= max_pages:
                await self.show_page(0)
            elif page_number < 0:
                await self.show_page(max_pages - 1)
            elif max_pages > page_number >= 0:
                await self.show_page(page_number)
        except IndexError:
            # An error happened that can be handled, so ignore it.
            pass

    def reaction_check(self, payload):
        """Just extends the default reaction_check to use owner_ids"""
        if payload.message_id != self.message.id:
            return False
        if payload.user_id not in (*self.bot.owner_ids, self._author_id):
            return False
        return payload.emoji in self.buttons

    def _skip_single_arrows(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages == 1

    def _skip_double_triangle_buttons(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages <= 2

    @menus.button(
        "\N{BLACK LEFT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}",
        position=menus.First(1),
        skip_if=_skip_single_arrows,
    )
    async def go_to_previous_page(self, payload):
        """go to the previous page"""
        await self.show_checked_page(self.current_page - 1)

    @menus.button(
        "\N{BLACK RIGHT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}",
        position=menus.Last(0),
        skip_if=_skip_single_arrows,
    )
    async def go_to_next_page(self, payload):
        """go to the next page"""
        await self.show_checked_page(self.current_page + 1)

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}",
        position=menus.First(0),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_first_page(self, payload):
        """go to the first page"""
        await self.show_page(0)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}",
        position=menus.Last(1),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_last_page(self, payload):
        """go to the last page"""
        # The call here is safe because it's guarded by skip_if
        await self.show_page(self._source.get_max_pages() - 1)

    @menus.button("\N{CROSS MARK}")
    async def stop_pages(self,
                         payload: discord.RawReactionActionEvent) -> None:
        """stops the pagination session."""
        self.stop()
        await self.message.delete()
Exemplo n.º 25
0
class Menu(menus.MenuPages, inherit_buttons=False):
    """A menus class for discord

    If you would like to change the buttons subclass this!
    """

    async def send_initial_message(self, ctx: commands.Context, channel: discord.TextChannel):
        self.current_page = self.page_start
        page = await self._source.get_page(self.page_start)
        kwargs = await self._get_kwargs_from_page(page)
        return await channel.send(**kwargs)

    async def show_checked_page(self, page_number: int):
        max_pages = self._source.get_max_pages()
        try:
            if max_pages is None or max_pages > page_number >= 0:
                await self.show_page(page_number)
            elif page_number >= max_pages:
                await self.show_page(0)
            else:
                await self.show_page(max_pages - 1)
        except IndexError:
            pass

    def _skip_double_triangle_buttons(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages <= 4

    def _skip_single_arrows(self):
        max_pages = self._source.get_max_pages()
        if max_pages is None:
            return True
        return max_pages == 1

    @menus.button(
        "\N{BLACK RIGHTWARDS ARROW}",
        position=menus.Last(0),
        skip_if=_skip_single_arrows,
    )
    async def go_to_next_page(self, payload):
        await self.show_checked_page(self.current_page + 1)

    @menus.button(
        "\N{LEFTWARDS BLACK ARROW}",
        position=menus.First(1),
        skip_if=_skip_single_arrows,
    )
    async def go_to_previous_page(self, payload):
        await self.show_checked_page(self.current_page - 1)

    @menus.button(
        "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE}",
        position=menus.Last(1),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_last_page(self, payload):
        await self.show_checked_page(self._source.get_max_pages - 1)

    @menus.button(
        "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE}",
        position=menus.First(0),
        skip_if=_skip_double_triangle_buttons,
    )
    async def go_to_first_page(self, payload):
        await self.show_checked_page(0)

    @menus.button("\N{CROSS MARK}")
    async def stop_pages(self, payload):
        self.stop()
        await self.message.delete()