From b21b820be1ad336471cef7f6829cd47e58c1df69 Mon Sep 17 00:00:00 2001 From: Raiden Sakura Date: Sun, 31 Mar 2024 15:54:35 +0800 Subject: [PATCH] Pull #3298 --- core/paginator.py | 129 ++++++++++++++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 43 deletions(-) diff --git a/core/paginator.py b/core/paginator.py index 4a40bd83c7..f0bc07a2cb 100644 --- a/core/paginator.py +++ b/core/paginator.py @@ -58,7 +58,7 @@ def __init__(self, ctx: commands.Context, *pages, **options): ">": self.next_page, ">>": self.last_page, } - self._buttons_map = {"<<": None, "<": None, ">": None, ">>": None} + self._buttons_map = {k: None for k in self.callback_map.keys()} async def show_page(self, index: int) -> typing.Optional[typing.Dict]: """ @@ -84,34 +84,19 @@ async def show_page(self, index: int) -> typing.Optional[typing.Dict]: self.update_disabled_status() return result - def update_disabled_status(self): - if self.current == self.first_page(): - # disable << button - if self._buttons_map["<<"] is not None: - self._buttons_map["<<"].disabled = True - - if self._buttons_map["<"] is not None: - self._buttons_map["<"].disabled = True - else: - if self._buttons_map["<<"] is not None: - self._buttons_map["<<"].disabled = False - - if self._buttons_map["<"] is not None: - self._buttons_map["<"].disabled = False - - if self.current == self.last_page(): - # disable >> button - if self._buttons_map[">>"] is not None: - self._buttons_map[">>"].disabled = True - - if self._buttons_map[">"] is not None: - self._buttons_map[">"].disabled = True - else: - if self._buttons_map[">>"] is not None: - self._buttons_map[">>"].disabled = False - - if self._buttons_map[">"] is not None: - self._buttons_map[">"].disabled = False + def update_disabled_status(self) -> None: + for label, button in self._buttons_map.items(): + if button is None: + continue + elif any( + ( + self.current == self.first_page() and label in ("<<", "<"), + self.current == self.last_page() and label in (">>", ">"), + ) + ): + button.disabled = True + else: + button.disabled = False async def create_base(self, item) -> None: """ @@ -227,7 +212,7 @@ def __init__(self, handler: PaginatorSession, *args, **kwargs): async def stop_button(self, interaction: Interaction, button: Button): await self.handler.close(interaction=interaction) - def fill_items(self): + def fill_items(self) -> None: if self.handler.select_menu is not None: self.add_item(self.handler.select_menu) @@ -246,7 +231,7 @@ def fill_items(self): self.add_item(button) self.add_item(self.stop_button) - async def interaction_check(self, interaction: Interaction): + async def interaction_check(self, interaction: Interaction) -> bool: """Only allow the message author to interact""" if interaction.user != self.handler.ctx.author: await interaction.response.send_message( @@ -280,34 +265,86 @@ def __init__(self, handler, page_callback, **kwargs): self.handler = handler self.page_callback = page_callback - async def callback(self, interaction: Interaction): + async def callback(self, interaction: Interaction) -> None: kwargs = await self.handler.show_page(self.page_callback()) + select_menu = self.handler.select_menu + if select_menu is not None: + select_menu.update_options(True) await interaction.response.edit_message(**kwargs, view=self.view) class PageSelect(Select): def __init__(self, handler: PaginatorSession, pages: typing.List[typing.Tuple[str]]): self.handler = handler - options = [] + self._all_options = [] # no limits for n, (label, description) in enumerate(pages): - options.append(discord.SelectOption(label=label, description=description, value=str(n))) + self._all_options.append(discord.SelectOption(label=label, description=description, value=str(n))) - options = options[:25] # max 25 options + options = self.update_options() super().__init__(placeholder="Select a page", min_values=1, max_values=1, options=options) - async def callback(self, interaction: Interaction): + def update_options(self, refresh_options: bool = False) -> typing.List[discord.SelectOption]: + """ + A helper to dynamically update the select menu options based on the current page. + """ + current = self.handler.current + differ = prev = after = 0 + + def max_reached(): + return prev + after >= 25 # max select options + + while not max_reached(): + differ += 1 + inc_prev = current - differ >= 0 + inc_next = current + differ <= len(self._all_options) + if not any((inc_prev, inc_next)): + break + if inc_prev and not max_reached(): + prev += 1 + if inc_next and not max_reached(): + after += 1 + + options = self._all_options[current - prev : current + after] + if refresh_options: + self.options.clear() + curr_option = self._all_options[current] + for option in options: + option.default = option == curr_option + self.append_option(option) + return options + + async def callback(self, interaction: Interaction) -> None: page = int(self.values[0]) kwargs = await self.handler.show_page(page) + self.update_options(True) await interaction.response.edit_message(**kwargs, view=self.view) class EmbedPaginatorSession(PaginatorSession): - def __init__(self, ctx: commands.Context, *embeds, **options): + """ + Class that interactively paginates embed pages. + This inherits from PaginatorSession. + Parameters + ---------- + ctx : Context + The context of the command. + embeds : List[discord.Embed] + A list of entries to paginate. + create_select : bool + Whether to create the select menu. Defaults to True. + """ + + def __init__( + self, + ctx: commands.Context, + *embeds: typing.List[discord.Embed], + create_select: bool = True, + **options, + ): super().__init__(ctx, *embeds, **options) if len(self.pages) > 1: select_options = [] - create_select = True for i, embed in enumerate(self.pages): footer_text = f"Page {i + 1} of {len(self.pages)}" if embed.footer.text: @@ -320,16 +357,22 @@ def __init__(self, ctx: commands.Context, *embeds, **options): embed.set_footer(text=footer_text, icon_url=icon_url) # select menu + if not create_select: + continue + if embed.author.name: title = embed.author.name[:30].strip() if len(embed.author.name) > 30: title += "..." - else: + elif embed.title: title = embed.title[:30].strip() if len(embed.title) > 30: title += "..." - if not title: - create_select = False + else: + title = None + + if not title: + create_select = False if embed.description: description = embed.description[:40].replace("*", "").replace("`", "").strip() @@ -352,7 +395,7 @@ def add_page(self, item: Embed) -> None: async def _create_base(self, item: Embed, view: View) -> None: self.base = await self.destination.send(embed=item, view=view) - def _show_page(self, page): + def _show_page(self, page) -> typing.Dict: return dict(embed=page) @@ -368,7 +411,7 @@ def add_page(self, item: str) -> None: else: raise TypeError("Page must be a str object.") - def _set_footer(self): + def _set_footer(self) -> None: if self.embed is not None: footer_text = f"Page {self.current+1} of {len(self.pages)}" if self.footer_text: