Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: paginator can now not use an embed #76

Merged
merged 7 commits into from
Dec 15, 2021
76 changes: 57 additions & 19 deletions modmail/utils/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)


Expand All @@ -57,13 +62,14 @@ def __init__(
contents: Union[List[str], str],
/,
source_message: Optional[discord.Message] = None,
embed: Embed = None,
embed: Optional[Embed] = _AUTOGENERATE,
onerandomusername marked this conversation as resolved.
Show resolved Hide resolved
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,
Expand All @@ -75,14 +81,31 @@ 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] = []
self.prefix = prefix
self.suffix = suffix
self.max_size = max_size
self.linesep = linesep
self.embed = embed or Embed()
if embed is _AUTOGENERATE:
self.embed = Embed()
else:
self.embed = embed
bast0006 marked this conversation as resolved.
Show resolved Hide resolved

# 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):
Expand Down Expand Up @@ -116,8 +139,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()
Expand All @@ -140,7 +163,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,
Expand All @@ -149,6 +172,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,
Expand All @@ -167,6 +191,7 @@ async def paginate(
prefix=prefix,
suffix=suffix,
max_size=max_size,
title=title,
linesep=linesep,
only_users=only_users,
only_roles=only_roles,
Expand All @@ -178,18 +203,24 @@ 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):
for item in paginator.children:
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)
Expand All @@ -212,15 +243,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:
bast0006 marked this conversation as resolved.
Show resolved Hide resolved
"""
Disable specific components depending on paginator page and length.
Expand All @@ -230,7 +252,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
Expand Down Expand Up @@ -264,7 +299,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:
Expand Down
29 changes: 0 additions & 29 deletions tests/modmail/utils/test_pagination.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import List, Union

import pytest

from modmail.utils.pagination import ButtonPaginator
Expand All @@ -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)}")