From 0d7267f565e35b5a1ab15c9004386550b7ff628b Mon Sep 17 00:00:00 2001 From: Krittick Date: Sat, 5 Mar 2022 18:49:45 -0800 Subject: [PATCH 1/8] initial framework for `Page` class --- discord/ext/pages/pagination.py | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/discord/ext/pages/pagination.py b/discord/ext/pages/pagination.py index 414bb9ba13..6de162a0aa 100644 --- a/discord/ext/pages/pagination.py +++ b/discord/ext/pages/pagination.py @@ -31,6 +31,7 @@ "Paginator", "PageGroup", "PaginatorMenu", + "Page", ) @@ -104,6 +105,44 @@ 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 + ): + 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. From 5d80b2f52743c0a88d2d606e2940ab3bba6eec99 Mon Sep 17 00:00:00 2001 From: Krittick Date: Sat, 5 Mar 2022 19:13:20 -0800 Subject: [PATCH 2/8] change get_page_content to automatically convert non-`Page` pages to `Page` pages. --- discord/ext/pages/pagination.py | 40 +++++++++++++++++---------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/discord/ext/pages/pagination.py b/discord/ext/pages/pagination.py index 6de162a0aa..3e012e82e4 100644 --- a/discord/ext/pages/pagination.py +++ b/discord/ext/pages/pagination.py @@ -21,7 +21,7 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from typing import Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union import discord from discord.ext.commands import Context @@ -627,17 +627,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, @@ -696,8 +698,8 @@ async def send( raise TypeError(f"expected bool not {mention_author.__class__!r}") 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 = ctx.author @@ -712,8 +714,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, @@ -757,22 +759,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, ) @@ -780,8 +782,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, ) From 6222ce9354366fe4e97c590960fbbe2033913da6 Mon Sep 17 00:00:00 2001 From: Krittick Date: Sat, 5 Mar 2022 19:33:09 -0800 Subject: [PATCH 3/8] more typing for new `Page` class --- discord/ext/pages/pagination.py | 48 ++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/discord/ext/pages/pagination.py b/discord/ext/pages/pagination.py index 3e012e82e4..012ff834f9 100644 --- a/discord/ext/pages/pagination.py +++ b/discord/ext/pages/pagination.py @@ -155,8 +155,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. @@ -189,7 +189,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, @@ -225,8 +225,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. @@ -272,7 +272,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, @@ -287,7 +287,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 @@ -295,7 +297,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 = {} @@ -323,7 +327,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, @@ -339,8 +343,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` @@ -365,7 +369,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 @@ -400,7 +404,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. @@ -417,8 +421,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: @@ -427,7 +431,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. @@ -445,8 +449,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: @@ -478,8 +482,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, ) @@ -698,8 +702,8 @@ async def send( raise TypeError(f"expected bool not {mention_author.__class__!r}") self.update_buttons() - page: Union[Page, str, discord.Embed, List[discord.Embed]] = self.pages[self.current_page] - page_content: Page = self.get_page_content(page) + page = self.pages[self.current_page] + page_content = self.get_page_content(page) self.user = ctx.author From f0e4b9a09c14f25e21afa1694c769b7220e04cbd Mon Sep 17 00:00:00 2001 From: Krittick Date: Sat, 5 Mar 2022 19:35:46 -0800 Subject: [PATCH 4/8] add example for new `Page` class usage --- examples/views/paginator.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) 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.""" From c2773b4ec111fbe00f779c40ac561497fd7f53e4 Mon Sep 17 00:00:00 2001 From: Krittick Date: Sat, 5 Mar 2022 19:40:18 -0800 Subject: [PATCH 5/8] remove unused import --- discord/ext/pages/pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/ext/pages/pagination.py b/discord/ext/pages/pagination.py index 012ff834f9..8839e9faa4 100644 --- a/discord/ext/pages/pagination.py +++ b/discord/ext/pages/pagination.py @@ -21,7 +21,7 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from typing import Any, Dict, List, Optional, Union +from typing import Dict, List, Optional, Union import discord from discord.ext.commands import Context From f79f3ee409bddda307f2bd976832d69d5fee9deb Mon Sep 17 00:00:00 2001 From: Krittick Date: Sat, 5 Mar 2022 19:41:33 -0800 Subject: [PATCH 6/8] update docstrings for `Page` --- discord/ext/pages/pagination.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discord/ext/pages/pagination.py b/discord/ext/pages/pagination.py index 8839e9faa4..075695bfee 100644 --- a/discord/ext/pages/pagination.py +++ b/discord/ext/pages/pagination.py @@ -106,7 +106,9 @@ async def callback(self, interaction: discord.Interaction): class Page: - """Represents a page shown in the paginator. Allows for directly referencing and modifying each page as a class instance. + """Represents a page shown in the paginator. + + Allows for directly referencing and modifying each page as a class instance. Parameters ---------- From 4919e825b4f3496e945d4c2c15c052d7fc75f7be Mon Sep 17 00:00:00 2001 From: Krittick Date: Sat, 5 Mar 2022 19:45:56 -0800 Subject: [PATCH 7/8] add check for content and embeds both being `None` --- discord/ext/pages/pagination.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/discord/ext/pages/pagination.py b/discord/ext/pages/pagination.py index 075695bfee..edba25c313 100644 --- a/discord/ext/pages/pagination.py +++ b/discord/ext/pages/pagination.py @@ -121,6 +121,8 @@ class Page: 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 From e8f1206ec9ead609129357173bd05b97b3c44df8 Mon Sep 17 00:00:00 2001 From: Krittick Date: Sat, 5 Mar 2022 19:48:15 -0800 Subject: [PATCH 8/8] add doc tables for `Page` --- docs/ext/pages/index.rst | 8 ++++++++ 1 file changed, 8 insertions(+) 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 ~~~~~~~~~