From 24f05ed56b053e1a770a6934381e85e4af309758 Mon Sep 17 00:00:00 2001 From: krittick Date: Sat, 5 Mar 2022 19:50:56 -0800 Subject: [PATCH] Merge pull request #1123 from krittick/pages-update-2 Add `Page` class to `ext.pages` to allow for greater flexibility with page contents --- discord/ext/pages/pagination.py | 123 ++++++++++++++++++++++---------- docs/ext/pages/index.rst | 8 +++ examples/views/paginator.py | 23 ++++++ 3 files changed, 117 insertions(+), 37 deletions(-) diff --git a/discord/ext/pages/pagination.py b/discord/ext/pages/pagination.py index 414bb9ba13..edba25c313 100644 --- a/discord/ext/pages/pagination.py +++ b/discord/ext/pages/pagination.py @@ -31,6 +31,7 @@ "Paginator", "PageGroup", "PaginatorMenu", + "Page", ) @@ -104,6 +105,48 @@ async def callback(self, interaction: discord.Interaction): await self.paginator.goto_page(page_number=self.paginator.current_page) +class Page: + """Represents a page shown in the paginator. + + Allows for directly referencing and modifying each page as a class instance. + + Parameters + ---------- + content: :class:`str` + The content of the page. Corresponds to the :class:`discord.Message.content` attribute. + embeds: Optional[List[Union[List[:class:`discord.Embed`], :class:`discord.Embed`]]] + The embeds of the page. Corresponds to the :class:`discord.Message.embeds` attribute. + """ + + def __init__( + self, content: Optional[str] = None, embeds: Optional[List[Union[List[discord.Embed], discord.Embed]]] = None + ): + if content is None and embeds is None: + raise discord.InvalidArgument("A page cannot have both content and embeds equal to None.") + self._content = content + self._embeds = embeds + + @property + def content(self) -> Optional[str]: + """Gets the content for the page.""" + return self._content + + @content.setter + def content(self, value: Optional[str]): + """Sets the content for the page.""" + self._content = value + + @property + def embeds(self) -> Optional[List[Union[List[discord.Embed], discord.Embed]]]: + """Gets the embeds for the page.""" + return self._embeds + + @embeds.setter + def embeds(self, value: Optional[List[Union[List[discord.Embed], discord.Embed]]]): + """Sets the embeds for the page.""" + self._embeds = value + + class PageGroup: """Creates a group of pages which the user can switch between. @@ -116,8 +159,8 @@ class PageGroup: Parameters ---------- - pages: Union[List[:class:`str`], List[Union[List[:class:`discord.Embed`], :class:`discord.Embed]]] - The list of strings, embeds, or list of embeds to include in the page group. + pages: Union[List[:class:`str`], List[:class:`Page`], List[Union[List[:class:`discord.Embed`], :class:`discord.Embed]]] + The list of :class:`Page` objects, strings, embeds, or list of embeds to include in the page group. label: :class:`str` The label shown on the corresponding PaginatorMenu dropdown option. Also used as the SelectOption value. @@ -150,7 +193,7 @@ class PageGroup: def __init__( self, - pages: Union[List[str], List[Union[List[discord.Embed], discord.Embed]]], + pages: Union[List[str], List[Page], List[Union[List[discord.Embed], discord.Embed]]], label: str, description: str, emoji: Union[str, discord.Emoji, discord.PartialEmoji] = None, @@ -186,8 +229,8 @@ class Paginator(discord.ui.View): Parameters ---------- - pages: Union[List[:class:`PageGroup`], List[:class:`str`], List[Union[List[:class:`discord.Embed`], :class:`discord.Embed`]]] - The list of :class:`PageGroup` objects, strings, embeds, or list of embeds to paginate. + pages: Union[List[:class:`PageGroup`], List[:class:`Page`], List[:class:`str`], List[Union[List[:class:`discord.Embed`], :class:`discord.Embed`]]] + The list of :class:`PageGroup` objects, :class:`Page` objects, strings, embeds, or list of embeds to paginate. If a list of :class:`PageGroup` objects is provided and `show_menu` is ``False``, only the first page group will be displayed. show_disabled: :class:`bool` Whether to show disabled buttons. @@ -233,7 +276,7 @@ class Paginator(discord.ui.View): def __init__( self, - pages: Union[List[PageGroup], List[str], List[Union[List[discord.Embed], discord.Embed]]], + pages: Union[List[PageGroup], List[Page], List[str], List[Union[List[discord.Embed], discord.Embed]]], show_disabled: bool = True, show_indicator=True, show_menu=False, @@ -248,7 +291,9 @@ def __init__( ) -> None: super().__init__(timeout=timeout) self.timeout: float = timeout - self.pages: Union[List[PageGroup], List[str], List[Union[List[discord.Embed], discord.Embed]]] = pages + self.pages: Union[ + List[PageGroup], List[str], List[Page], List[Union[List[discord.Embed], discord.Embed]] + ] = pages self.current_page = 0 self.menu: Optional[PaginatorMenu] = None self.show_menu = show_menu @@ -256,7 +301,9 @@ def __init__( if all(isinstance(pg, PageGroup) for pg in pages): self.page_groups = self.pages if show_menu else None - self.pages: Union[List[str], List[Union[List[discord.Embed], discord.Embed]]] = self.page_groups[0].pages + self.pages: Union[ + List[str], List[Page], List[Union[List[discord.Embed], discord.Embed]] + ] = self.page_groups[0].pages self.page_count = len(self.pages) - 1 self.buttons = {} @@ -284,7 +331,7 @@ def __init__( async def update( self, - pages: Optional[Union[List[str], List[Union[List[discord.Embed], discord.Embed]]]] = None, + pages: Optional[Union[List[str], List[Page], List[Union[List[discord.Embed], discord.Embed]]]] = None, show_disabled: Optional[bool] = None, show_indicator: Optional[bool] = None, author_check: Optional[bool] = None, @@ -300,8 +347,8 @@ async def update( Parameters ---------- - pages: Optional[Union[List[:class:`PageGroup`], List[:class:`str`], List[Union[List[:class:`discord.Embed`], :class:`discord.Embed]]]] - The list of :class:`PageGroup` objects, strings, embeds, or list of embeds to paginate. + pages: Optional[Union[List[:class:`PageGroup`], List[:class:`Page`], List[:class:`str`], List[Union[List[:class:`discord.Embed`], :class:`discord.Embed]]]] + The list of :class:`PageGroup` objects, :class:`Page` objects, strings, embeds, or list of embeds to paginate. show_disabled: :class:`bool` Whether to show disabled buttons. show_indicator: :class:`bool` @@ -326,7 +373,7 @@ async def update( """ # Update pages and reset current_page to 0 (default) - self.pages: Union[List[PageGroup], List[str], List[Union[List[discord.Embed], discord.Embed]]] = ( + self.pages: Union[List[PageGroup], List[str], List[Page], List[Union[List[discord.Embed], discord.Embed]]] = ( pages if pages is not None else self.pages ) self.page_count = len(self.pages) - 1 @@ -361,7 +408,7 @@ async def on_timeout(self) -> None: async def disable( self, include_custom: bool = False, - page: Optional[Union[str, Union[List[discord.Embed], discord.Embed]]] = None, + page: Optional[Union[str, Page, Union[List[discord.Embed], discord.Embed]]] = None, ) -> None: """Stops the paginator, disabling all of its components. @@ -378,8 +425,8 @@ async def disable( item.disabled = True if page: await self.message.edit( - content=page if isinstance(page, str) else None, - embeds=[] if isinstance(page, str) else page, + content=page.content, + embeds=page.embeds, view=self, ) else: @@ -388,7 +435,7 @@ async def disable( async def cancel( self, include_custom: bool = False, - page: Optional[Union[str, Union[List[discord.Embed], discord.Embed]]] = None, + page: Optional[Union[str, Page, Union[List[discord.Embed], discord.Embed]]] = None, ) -> None: """Cancels the paginator, removing all of its components from the message. @@ -406,8 +453,8 @@ async def cancel( self.remove_item(item) if page: await self.message.edit( - content=page if isinstance(page, str) else None, - embeds=[] if isinstance(page, str) else page, + content=page.content, + embeds=page.embeds, view=self, ) else: @@ -439,8 +486,8 @@ async def goto_page(self, page_number=0) -> discord.Message: page = self.get_page_content(page) return await self.message.edit( - content=page if isinstance(page, str) else None, - embeds=[] if isinstance(page, str) else page, + content=page.content, + embeds=page.embeds, view=self, ) @@ -588,17 +635,19 @@ def update_buttons(self) -> Dict: return self.buttons @staticmethod - def get_page_content(page: Union[str, discord.Embed, List[discord.Embed]]): + def get_page_content(page: Union[Page, str, discord.Embed, List[discord.Embed]]) -> Page: """Returns the correct content type for a page based on its content.""" - if isinstance(page, discord.Embed): - return [page] + if isinstance(page, Page): + return page + elif isinstance(page, str): + return Page(content=page, embeds=[]) + elif isinstance(page, discord.Embed): + return Page(content=None, embeds=[page]) elif isinstance(page, List): if all(isinstance(x, discord.Embed) for x in page): - return page + return Page(content=None, embeds=page) else: raise TypeError("All list items must be embeds.") - elif isinstance(page, str): - return page async def send( self, @@ -658,7 +707,7 @@ async def send( self.update_buttons() page = self.pages[self.current_page] - page = self.get_page_content(page) + page_content = self.get_page_content(page) self.user = ctx.author @@ -673,8 +722,8 @@ async def send( ctx = target self.message = await ctx.send( - content=page if isinstance(page, str) else None, - embeds=[] if isinstance(page, str) else page, + content=page_content.content, + embeds=page_content.embeds, view=self, reference=reference, allowed_mentions=allowed_mentions, @@ -718,22 +767,22 @@ async def respond( self.update_buttons() - page = self.pages[self.current_page] - page = self.get_page_content(page) + page: Union[Page, str, discord.Embed, List[discord.Embed]] = self.pages[self.current_page] + page_content: Page = self.get_page_content(page) self.user = interaction.user if target: await interaction.response.send_message(target_message, ephemeral=ephemeral) self.message = await target.send( - content=page if isinstance(page, str) else None, - embeds=[] if isinstance(page, str) else page, + content=page_content.content, + embeds=page_content.embeds, view=self, ) else: if interaction.response.is_done(): msg = await interaction.followup.send( - content=page if isinstance(page, str) else None, - embeds=[] if isinstance(page, str) else page, + content=page_content.content, + embeds=page_content.embeds, view=self, ephemeral=ephemeral, ) @@ -741,8 +790,8 @@ async def respond( msg = await msg.channel.fetch_message(msg.id) else: msg = await interaction.response.send_message( - content=page if isinstance(page, str) else None, - embeds=[] if isinstance(page, str) else page, + content=page_content.content, + embeds=page_content.embeds, view=self, ephemeral=ephemeral, ) diff --git a/docs/ext/pages/index.rst b/docs/ext/pages/index.rst index fc4022bf99..ea969d58cd 100644 --- a/docs/ext/pages/index.rst +++ b/docs/ext/pages/index.rst @@ -291,6 +291,14 @@ Example usage in a cog: API Reference ------------- +Page +~~~~ + +.. attributetable:: discord.ext.pages.Page + +.. autoclass:: discord.ext.pages.Page + :members: + Paginator ~~~~~~~~~ diff --git a/examples/views/paginator.py b/examples/views/paginator.py index 02fedaf1c8..500539b49a 100644 --- a/examples/views/paginator.py +++ b/examples/views/paginator.py @@ -36,6 +36,23 @@ def __init__(self, bot): self.even_more_pages = ["11111", "22222", "33333"] + self.new_pages = [ + pages.Page( + content="Page 1 Title!", + embeds=[ + discord.Embed(title="New Page 1 Embed Title 1!"), + discord.Embed(title="New Page 1 Embed Title 2!"), + ], + ), + pages.Page( + content="Page 2 Title!", + embeds=[ + discord.Embed(title="New Page 2 Embed Title 1!"), + discord.Embed(title="New Page 2 Embed Title 2!"), + ], + ), + ] + def get_pages(self): return self.pages @@ -48,6 +65,12 @@ async def pagetest_default(self, ctx: discord.ApplicationContext): paginator = pages.Paginator(pages=self.get_pages()) await paginator.respond(ctx.interaction, ephemeral=False) + @pagetest.command(name="new") + async def pagetest_new(self, ctx: discord.ApplicationContext): + """Demonstrates using the paginator with the Page class.""" + paginator = pages.Paginator(pages=self.new_pages) + await paginator.respond(ctx.interaction, ephemeral=False) + @pagetest.command(name="hidden") async def pagetest_hidden(self, ctx: discord.ApplicationContext): """Demonstrates using the paginator with disabled buttons hidden."""