aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Amrou Bellalouna <[email protected]>2024-03-11 15:17:54 +0100
committerGravatar GitHub <[email protected]>2024-03-11 14:17:54 +0000
commit4a2f8630b1e934a7e77111f5017734c298485f6c (patch)
treea06c86c18e36cc580bc2b3fb7d819a313c702bda
parentBump rapidfuzz from 3.6.1 to 3.6.2 (#1470) (diff)
Use paginator from botcore (#1444)
-rw-r--r--bot/exts/core/help.py17
-rw-r--r--bot/utils/pagination.py313
2 files changed, 59 insertions, 271 deletions
diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py
index 7721d200..2960d722 100644
--- a/bot/exts/core/help.py
+++ b/bot/exts/core/help.py
@@ -11,19 +11,16 @@ from pydis_core.utils.logging import get_logger
from bot import constants
from bot.bot import Bot
-from bot.constants import Emojis
from bot.utils.commands import get_command_suggestions
from bot.utils.decorators import whitelist_override
-from bot.utils.pagination import FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI
-
-DELETE_EMOJI = Emojis.trashcan
+from bot.utils.pagination import LinePaginator, PAGINATION_EMOJI
REACTIONS = {
- FIRST_EMOJI: "first",
- LEFT_EMOJI: "back",
- RIGHT_EMOJI: "next",
- LAST_EMOJI: "end",
- DELETE_EMOJI: "stop",
+ PAGINATION_EMOJI.first: "first",
+ PAGINATION_EMOJI.left: "back",
+ PAGINATION_EMOJI.right: "next",
+ PAGINATION_EMOJI.last: "end",
+ PAGINATION_EMOJI.delete: "stop",
}
@@ -236,7 +233,7 @@ class HelpSession:
# if single-page
else:
- self._bot.loop.create_task(self.message.add_reaction(DELETE_EMOJI))
+ self._bot.loop.create_task(self.message.add_reaction(PAGINATION_EMOJI.delete))
def _category_key(self, cmd: Command) -> str:
"""
diff --git a/bot/utils/pagination.py b/bot/utils/pagination.py
index b2da37ed..dca08568 100644
--- a/bot/utils/pagination.py
+++ b/bot/utils/pagination.py
@@ -1,276 +1,66 @@
-from collections.abc import Iterable
+from collections.abc import Sequence
-from discord import Embed, Member, Reaction
+from discord import Embed, Interaction, Member, Message, Reaction
from discord.abc import User
from discord.ext.commands import Context, Paginator
from pydis_core.utils.logging import get_logger
+from pydis_core.utils.pagination import EmptyPaginatorEmbedError, LinePaginator as _LinePaginator, PaginationEmojis
from bot.constants import Emojis
-FIRST_EMOJI = "\u23EE" # [:track_previous:]
-LEFT_EMOJI = "\u2B05" # [:arrow_left:]
-RIGHT_EMOJI = "\u27A1" # [:arrow_right:]
-LAST_EMOJI = "\u23ED" # [:track_next:]
-DELETE_EMOJI = Emojis.trashcan # [:trashcan:]
-
-PAGINATION_EMOJI = (FIRST_EMOJI, LEFT_EMOJI, RIGHT_EMOJI, LAST_EMOJI, DELETE_EMOJI)
+PAGINATION_EMOJI = PaginationEmojis(delete=Emojis.trashcan)
log = get_logger(__name__)
-class EmptyPaginatorEmbedError(Exception):
- """Base Exception class for an empty paginator embed."""
-
-
-class LinePaginator(Paginator):
+class LinePaginator(_LinePaginator):
"""A class that aids in paginating code blocks for Discord messages."""
- def __init__(
- self,
- prefix: str = "```",
- suffix: str = "```",
- max_size: int = 2000,
- max_lines: int | None = None,
- linesep: str = "\n"
- ):
- """
- Overrides the Paginator.__init__ from inside discord.ext.commands.
-
- `prefix` and `suffix` will be prepended and appended respectively to every page.
-
- `max_size` and `max_lines` denote the maximum amount of codepoints and lines
- allowed per page.
- """
- super().__init__(
- prefix,
- suffix,
- max_size - len(suffix),
- linesep
- )
-
- self.max_lines = max_lines
- self._current_page = [prefix]
- self._linecount = 0
- self._count = len(prefix) + 1 # prefix + newline
- self._pages = []
-
- def add_line(self, line: str = "", *, empty: bool = False) -> None:
- """
- Adds a line to the current page.
-
- If the line exceeds the `max_size` then a RuntimeError is raised.
-
- Overrides the Paginator.add_line from inside discord.ext.commands in order to allow
- configuration of the maximum number of lines per page.
-
- If `empty` is True, an empty line will be placed after the a given `line`.
- """
- if len(line) > self.max_size - len(self.prefix) - 2:
- raise RuntimeError("Line exceeds maximum page size %s" % (self.max_size - len(self.prefix) - 2))
-
- if self.max_lines is not None:
- if self._linecount >= self.max_lines:
- self._linecount = 0
- self.close_page()
-
- self._linecount += 1
- if self._count + len(line) + 1 > self.max_size:
- self.close_page()
-
- self._count += len(line) + 1
- self._current_page.append(line)
-
- if empty:
- self._current_page.append("")
- self._count += 1
-
@classmethod
async def paginate(
- cls, lines: Iterable[str], ctx: Context,
- embed: Embed, prefix: str = "", suffix: str = "",
- max_lines: int | None = None, max_size: int = 500, empty: bool = True,
- restrict_to_user: User = None, timeout: float = 300, footer_text: str | None = None,
- url: str | None = None, exception_on_empty_embed: bool = False
- ) -> None:
+ cls,
+ lines: list[str],
+ ctx: Context | Interaction,
+ embed: Embed,
+ prefix: str = "",
+ suffix: str = "",
+ max_lines: int | None = None,
+ max_size: int = 500,
+ scale_to_size: int = 4000,
+ empty: bool = True,
+ restrict_to_user: User | None = None,
+ timeout: int = 300,
+ footer_text: str | None = None,
+ url: str | None = None,
+ exception_on_empty_embed: bool = False,
+ reply: bool = False,
+ allowed_roles: Sequence[int] | None = None,
+ ) -> Message | None:
"""
Use a paginator and set of reactions to provide pagination over a set of lines.
- The reactions are used to switch page, or to finish with pagination.
-
- When used, this will send a message using `ctx.send()` and apply a set of reactions to it.
- These reactions may be used to change page, or to remove pagination from the message.
-
- Pagination will also be removed automatically if no reaction is added for `timeout` seconds,
- defaulting to five minutes (300 seconds).
-
- If `empty` is True, an empty line will be placed between each given line.
-
- >>> embed = Embed()
- >>> embed.set_author(name="Some Operation", url=url, icon_url=icon)
- >>> await LinePaginator.paginate(
- ... (line for line in lines),
- ... ctx, embed
- ... )
+ Acts as a wrapper for the super class' `paginate` method to provide the pagination emojis by default.
+ Consult the super class's `paginate` method for detailed information.
"""
- def event_check(reaction_: Reaction, user_: Member) -> bool:
- """Make sure that this reaction is what we want to operate on."""
- no_restrictions = (
- # Pagination is not restricted
- not restrict_to_user
- # The reaction was by a whitelisted user
- or user_.id == restrict_to_user.id
- )
-
- return (
- # Conditions for a successful pagination:
- all((
- # Reaction is on this message
- reaction_.message.id == message.id,
- # Reaction is one of the pagination emotes
- str(reaction_.emoji) in PAGINATION_EMOJI, # Note: DELETE_EMOJI is a string and not unicode
- # Reaction was not made by the Bot
- user_.id != ctx.bot.user.id,
- # There were no restrictions
- no_restrictions
- ))
- )
-
- paginator = cls(prefix=prefix, suffix=suffix, max_size=max_size, max_lines=max_lines)
- current_page = 0
-
- if not lines:
- if exception_on_empty_embed:
- log.exception("Pagination asked for empty lines iterable")
- raise EmptyPaginatorEmbedError("No lines to paginate")
-
- log.debug("No lines to add to paginator, adding '(nothing to display)' message")
- lines.append("(nothing to display)")
-
- for line in lines:
- try:
- paginator.add_line(line, empty=empty)
- except Exception:
- log.exception(f"Failed to add line to paginator: '{line}'")
- raise # Should propagate
- else:
- log.trace(f"Added line to paginator: '{line}'")
-
- log.debug(f"Paginator created with {len(paginator.pages)} pages")
-
- embed.description = paginator.pages[current_page]
-
- if len(paginator.pages) <= 1:
- if footer_text:
- embed.set_footer(text=footer_text)
- log.trace(f"Setting embed footer to '{footer_text}'")
-
- if url:
- embed.url = url
- log.trace(f"Setting embed url to '{url}'")
-
- log.debug("There's less than two pages, so we won't paginate - sending single page on its own")
- await ctx.send(embed=embed)
- return None
-
- if footer_text:
- embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})")
- else:
- embed.set_footer(text=f"Page {current_page + 1}/{len(paginator.pages)}")
- log.trace(f"Setting embed footer to '{embed.footer.text}'")
-
- if url:
- embed.url = url
- log.trace(f"Setting embed url to '{url}'")
-
- log.debug("Sending first page to channel...")
- message = await ctx.send(embed=embed)
-
- log.debug("Adding emoji reactions to message...")
-
- for emoji in PAGINATION_EMOJI:
- # Add all the applicable emoji to the message
- log.trace(f"Adding reaction: {emoji!r}")
- await message.add_reaction(emoji)
-
- while True:
- try:
- reaction, user = await ctx.bot.wait_for("reaction_add", timeout=timeout, check=event_check)
- log.trace(f"Got reaction: {reaction}")
- except TimeoutError:
- log.debug("Timed out waiting for a reaction")
- break # We're done, no reactions for the last 5 minutes
-
- if str(reaction.emoji) == DELETE_EMOJI: # Note: DELETE_EMOJI is a string and not unicode
- log.debug("Got delete reaction")
- return await message.delete()
-
- if reaction.emoji == FIRST_EMOJI:
- await message.remove_reaction(reaction.emoji, user)
- current_page = 0
-
- log.debug(f"Got first page reaction - changing to page 1/{len(paginator.pages)}")
-
- embed.description = paginator.pages[current_page]
- if footer_text:
- embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})")
- else:
- embed.set_footer(text=f"Page {current_page + 1}/{len(paginator.pages)}")
- await message.edit(embed=embed)
-
- if reaction.emoji == LAST_EMOJI:
- await message.remove_reaction(reaction.emoji, user)
- current_page = len(paginator.pages) - 1
-
- log.debug(f"Got last page reaction - changing to page {current_page + 1}/{len(paginator.pages)}")
-
- embed.description = paginator.pages[current_page]
- if footer_text:
- embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})")
- else:
- embed.set_footer(text=f"Page {current_page + 1}/{len(paginator.pages)}")
- await message.edit(embed=embed)
-
- if reaction.emoji == LEFT_EMOJI:
- await message.remove_reaction(reaction.emoji, user)
-
- if current_page <= 0:
- log.debug("Got previous page reaction, but we're on the first page - ignoring")
- continue
-
- current_page -= 1
- log.debug(f"Got previous page reaction - changing to page {current_page + 1}/{len(paginator.pages)}")
-
- embed.description = paginator.pages[current_page]
-
- if footer_text:
- embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})")
- else:
- embed.set_footer(text=f"Page {current_page + 1}/{len(paginator.pages)}")
-
- await message.edit(embed=embed)
-
- if reaction.emoji == RIGHT_EMOJI:
- await message.remove_reaction(reaction.emoji, user)
-
- if current_page >= len(paginator.pages) - 1:
- log.debug("Got next page reaction, but we're on the last page - ignoring")
- continue
-
- current_page += 1
- log.debug(f"Got next page reaction - changing to page {current_page + 1}/{len(paginator.pages)}")
-
- embed.description = paginator.pages[current_page]
-
- if footer_text:
- embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})")
- else:
- embed.set_footer(text=f"Page {current_page + 1}/{len(paginator.pages)}")
-
- await message.edit(embed=embed)
-
- log.debug("Ending pagination and clearing reactions...")
- await message.clear_reactions()
- return None
+ return await super().paginate(
+ pagination_emojis=PAGINATION_EMOJI,
+ lines=lines,
+ ctx=ctx,
+ embed=embed,
+ prefix=prefix,
+ suffix=suffix,
+ max_lines=max_lines,
+ max_size=max_size,
+ scale_to_size=scale_to_size,
+ empty=empty,
+ restrict_to_user=restrict_to_user,
+ timeout=timeout,
+ footer_text=footer_text,
+ url=url,
+ exception_on_empty_embed=exception_on_empty_embed,
+ reply=reply,
+ allowed_roles=allowed_roles,
+ )
class ImagePaginator(Paginator):
@@ -331,7 +121,8 @@ class ImagePaginator(Paginator):
# Reaction is on the same message sent
reaction_.message.id == message.id,
# The reaction is part of the navigation menu
- str(reaction_.emoji) in PAGINATION_EMOJI, # Note: DELETE_EMOJI is a string and not unicode
+ # Note: DELETE_EMOJI is a string and not unicode
+ str(reaction_.emoji) in PAGINATION_EMOJI.model_dump().values(),
# The reactor is not a bot
not member.bot
))
@@ -364,7 +155,7 @@ class ImagePaginator(Paginator):
embed.set_footer(text=f"Page {current_page + 1}/{len(paginator.pages)}")
message = await ctx.send(embed=embed)
- for emoji in PAGINATION_EMOJI:
+ for emoji in PAGINATION_EMOJI.model_dump().values():
await message.add_reaction(emoji)
while True:
@@ -379,12 +170,12 @@ class ImagePaginator(Paginator):
await message.remove_reaction(reaction.emoji, user)
# Delete reaction press - [:trashcan:]
- if str(reaction.emoji) == DELETE_EMOJI: # Note: DELETE_EMOJI is a string and not unicode
+ if str(reaction.emoji) == PAGINATION_EMOJI.delete: # Note: DELETE_EMOJI is a string and not unicode
log.debug("Got delete reaction")
return await message.delete()
# First reaction press - [:track_previous:]
- if reaction.emoji == FIRST_EMOJI:
+ if reaction.emoji == PAGINATION_EMOJI.first:
if current_page == 0:
log.debug("Got first page reaction, but we're on the first page - ignoring")
continue
@@ -393,7 +184,7 @@ class ImagePaginator(Paginator):
reaction_type = "first"
# Last reaction press - [:track_next:]
- if reaction.emoji == LAST_EMOJI:
+ if reaction.emoji == PAGINATION_EMOJI.last:
if current_page >= len(paginator.pages) - 1:
log.debug("Got last page reaction, but we're on the last page - ignoring")
continue
@@ -402,7 +193,7 @@ class ImagePaginator(Paginator):
reaction_type = "last"
# Previous reaction press - [:arrow_left: ]
- if reaction.emoji == LEFT_EMOJI:
+ if reaction.emoji == PAGINATION_EMOJI.left:
if current_page <= 0:
log.debug("Got previous page reaction, but we're on the first page - ignoring")
continue
@@ -411,7 +202,7 @@ class ImagePaginator(Paginator):
reaction_type = "previous"
# Next reaction press - [:arrow_right:]
- if reaction.emoji == RIGHT_EMOJI:
+ if reaction.emoji == PAGINATION_EMOJI.right:
if current_page >= len(paginator.pages) - 1:
log.debug("Got next page reaction, but we're on the last page - ignoring")
continue