diff --git a/modmail/utils/pagination.py b/modmail/utils/pagination.py index e0f633a4..8207ec14 100644 --- a/modmail/utils/pagination.py +++ b/modmail/utils/pagination.py @@ -31,6 +31,11 @@ JUMP_LAST_LABEL = " \u276f\u276f " # >> STOP_PAGINATE_EMOJI = "\u274c" # [:x:] This is an emoji, which is treated differently from the above +NO_EMBED_FOOTER_BUMP = 15 + +_AUTOGENERATE = object() + + logger: ModmailLogger = logging.getLogger(__name__) @@ -57,13 +62,14 @@ def __init__( contents: Union[List[str], str], /, source_message: Optional[discord.Message] = None, - embed: Embed = None, + embed: Union[Embed, bool, None] = _AUTOGENERATE, timeout: float = 180, *, footer_text: str = None, prefix: str = "```", suffix: str = "```", max_size: int = 2000, + title: str = None, linesep: str = "\n", only_users: Optional[List[Union[discord.Object, discord.abc.User]]] = None, only_roles: Optional[List[Union[discord.Object, discord.Role]]] = None, @@ -75,6 +81,8 @@ def __init__( If source message is provided and only_users is NOT provided, the paginator will respond to the author of the source message. To override this, pass an empty list to `only_users`. + By default, an embed is created. However, a custom embed can + be passed, or None can be passed to not use an embed. """ self.index = 0 self._pages: List[str] = [] @@ -82,7 +90,24 @@ def __init__( self.suffix = suffix self.max_size = max_size self.linesep = linesep - self.embed = embed or Embed() + if embed is _AUTOGENERATE or embed is True: + self.embed = Embed() + else: + if embed is False: + embed = None + self.embed = embed + + # used if embed is None + self.content = "" + if self.embed is None: + self.title = title + # need to set the max_size down a few to be able to set a "footer" + # page indicator is "page xx of xx" + self.max_size -= NO_EMBED_FOOTER_BUMP + len(self.title or "") + if self.title is not None: + self.max_size -= len(title) + if footer_text is not None: + self.max_size -= len(footer_text) + 1 # temporary to support strings as contents. This will be changed when we added wrapping. if isinstance(contents, str): @@ -116,8 +141,8 @@ def __init__( # set footer to embed.footer if embed is set # this is because we will be modifying the footer of this embed - if embed is not None: - if not isinstance(embed.footer, EmbedProxy) and footer_text is None: + if self.embed is not None: + if not isinstance(self.embed.footer, EmbedProxy) and footer_text is None: footer_text = embed.footer self.footer_text = footer_text self.clear() @@ -140,7 +165,7 @@ async def paginate( source_message: discord.Message = None, /, timeout: float = 180, - embed: Embed = None, + embed: Embed = _AUTOGENERATE, *, footer_text: str = None, only: Optional[discord.abc.User] = None, @@ -149,6 +174,7 @@ async def paginate( prefix: str = "", suffix: str = "", max_size: int = 4000, + title: str = None, linesep: str = "\n", only_users: Optional[List[Union[discord.Object, discord.abc.User]]] = None, only_roles: Optional[List[Union[discord.Object, discord.abc.Role]]] = None, @@ -167,6 +193,7 @@ async def paginate( prefix=prefix, suffix=suffix, max_size=max_size, + title=title, linesep=linesep, only_users=only_users, only_roles=only_roles, @@ -178,10 +205,13 @@ async def paginate( channel = source_message.channel paginator.update_states() - paginator.embed.description = paginator.pages[paginator.index] # if there's only one page, don't send the view if len(paginator.pages) < 2: - await channel.send(embeds=[paginator.embed]) + if paginator.embed: + await channel.send(embeds=[paginator.embed]) + else: + await channel.send(content=paginator.content) + return if len(paginator.pages) < (show_jump_buttons_min_pages or 3): @@ -189,7 +219,10 @@ async def paginate( if getattr(item, "custom_id", None) in ["pag_jump_first", "pag_jump_last"]: paginator.remove_item(item) - msg: discord.Message = await channel.send(embeds=[paginator.embed], view=paginator) + if paginator.embed is None: + msg: discord.Message = await channel.send(content=paginator.content, view=paginator) + else: + msg: discord.Message = await channel.send(embeds=[paginator.embed], view=paginator) await paginator.wait() await msg.edit(view=None) @@ -212,15 +245,6 @@ async def interaction_check(self, interaction: Interaction) -> bool: ) return False - def get_footer(self) -> str: - """Returns the footer text.""" - self.embed.description = self._pages[self.index] - page_indicator = f"Page {self.index+1}/{len(self._pages)}" - footer_txt = ( - f"{self.footer_text} ({page_indicator})" if self.footer_text is not None else page_indicator - ) - return footer_txt - def update_states(self) -> None: """ Disable specific components depending on paginator page and length. @@ -230,7 +254,20 @@ def update_states(self) -> None: if the paginator is on the last page, the jump last/move forward buttons will be disabled. """ # update the footer - self.embed.set_footer(text=self.get_footer()) + page_indicator = f"Page {self.index+1}/{len(self._pages)}" + if self.footer_text: + footer_text = f"{self.footer_text} ({page_indicator})" + else: + footer_text = page_indicator + + if self.embed is None: + self.content = (self.title or "") + "\n" + self.content += self._pages[self.index] + self.content += "\n" + footer_text + + else: + self.embed.description = self._pages[self.index] + self.embed.set_footer(text=footer_text) # determine if the jump buttons should be enabled more_than_two_pages = len(self._pages) > 2 @@ -264,7 +301,10 @@ async def send_page(self, interaction: Interaction) -> None: """Send new page to discord, after updating the view to have properly disabled buttons.""" self.update_states() - await interaction.message.edit(embed=self.embed, view=self) + if self.embed: + await interaction.message.edit(embed=self.embed, view=self) + else: + await interaction.message.edit(content=self.content, view=self) @ui.button(label=JUMP_FIRST_LABEL, custom_id="pag_jump_first", style=ButtonStyle.primary) async def go_first(self, _: Button, interaction: Interaction) -> None: diff --git a/tests/modmail/utils/test_pagination.py b/tests/modmail/utils/test_pagination.py index 1993c6ee..2ada1079 100644 --- a/tests/modmail/utils/test_pagination.py +++ b/tests/modmail/utils/test_pagination.py @@ -1,5 +1,3 @@ -from typing import List, Union - import pytest from modmail.utils.pagination import ButtonPaginator @@ -11,30 +9,3 @@ async def test_paginator_init() -> None: content = ["content"] paginator = ButtonPaginator(content, prefix="", suffix="", linesep="") assert paginator.pages == content - - -@pytest.mark.asyncio -@pytest.mark.parametrize( - "content, footer_text", - [ - (["5"], "Snap, crackle, pop"), - (["Earthly"], "world"), - ("There are no plugins installed.", None), - ], -) -async def test_paginator_footer(content: Union[str, List[str]], footer_text: str) -> None: - """Test the paginator footer matches what is passed.""" - pag = ButtonPaginator(content, footer_text=footer_text) - print("index:", pag.index) - print("page len: ", len(pag.pages)) - assert pag.footer_text == footer_text - if isinstance(content, str): - content = [content] - - print(pag.get_footer()) - if footer_text is not None: - assert pag.get_footer().endswith(f"{len(content)})") - assert pag.get_footer().startswith(footer_text) - - else: - assert pag.get_footer().endswith(f"{len(content)}")