| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- """
- Provides a means of presenting long messages by paging them. Paging requires the
- source message to insert `PAGE_BREAK` characters at meaningful breaks, preferably
- at fairly uniform intervals.
- """
-
- from typing import Optional
-
- from discord import Interaction
- from discord.ui import LayoutView, TextDisplay, ActionRow, Button
-
- from rocketbot.utils import dump_stacktrace
-
- PAGE_BREAK = '\f'
-
- def paginate(text: str) -> list[str]:
- """
- Breaks long message text into one or more pages, using page break markers as
- potential clean break points.
- """
- max_page_size = 2000
- chunks = text.split(PAGE_BREAK)
- pages = [ '' ]
- for chunk in chunks:
- if len(chunk) > max_page_size:
- raise ValueError('Help content needs more page breaks! One chunk is too big for message.')
- if len(pages[-1] + chunk) < max_page_size:
- pages[-1] += chunk
- else:
- pages.append(chunk)
- page_count = len(pages)
- if page_count == 1:
- return pages
-
- # Do another pass and try to even out the page lengths
- indices = [ i * len(chunks) // page_count for i in range(page_count + 1) ]
- even_pages = [
- ''.join(chunks[indices[i]:indices[i + 1]])
- for i in range(page_count)
- ]
- for page in even_pages:
- if len(page) > max_page_size:
- # We made a page too big. Give up.
- return pages
- return even_pages
-
- async def update_paged_content(
- interaction: Interaction,
- original_interaction: Optional[Interaction],
- current_page: int,
- pages: list[str],
- **send_args,
- ) -> None:
- """
- Posts and/or updates the content of a message from paged content.
-
- Parameters
- ----------
- interaction : Interaction
- the current interaction, either the initial one or from a button press
- original_interaction : Interaction
- the first interaction that triggered presentation of the content, or None
- if this already is the first interaction
- current_page : int
- page index to show (assumed to be in bounds)
- pages : list[str]
- array of page content
- **send_args
- additional arguments to pass to the send_message method
- """
- if len(pages) == 1:
- # No paging needed
- await interaction.response.send_message(
- pages[0],
- ephemeral=True,
- )
- return
-
- try:
- view = _PagingLayoutView(current_page, pages, original_interaction or interaction)
- resolved = interaction
- if original_interaction is not None:
- # We have an original interaction from the initial command and a
- # new one from the button press. Use the original to swap in the
- # new page in place, then acknowledge the new one to satisfy the
- # API that we didn't fail.
- await original_interaction.edit_original_response(
- view=view,
- )
- if interaction is not original_interaction:
- await interaction.response.defer(ephemeral=True, thinking=False)
- else:
- # Initial send
- await resolved.response.send_message(
- view=view,
- ephemeral=True,
- **send_args,
- )
- except BaseException as e:
- dump_stacktrace(e)
-
- class _PagingLayoutView(LayoutView):
- def __init__(
- self,
- current_page: int,
- pages: list[str],
- original_interaction: Optional[Interaction],
- **send_args,
- ) -> None:
- super().__init__()
- self.current_page: int = current_page
- self.pages: list[str] = pages
- self.text.content = self.pages[self.current_page] + f'\n\n_Page {self.current_page + 1} of {len(self.pages)}_'
- self.original_interaction = original_interaction
- if current_page <= 0:
- self.handle_prev_button.disabled = True
- if current_page >= len(self.pages) - 1:
- self.handle_next_button.disabled = True
- self.send_args = send_args
-
- text = TextDisplay('')
-
- row = ActionRow()
-
- @row.button(label='< Prev')
- async def handle_prev_button(self, interaction: Interaction, button: Button) -> None:
- new_page = max(0, self.current_page - 1)
- await update_paged_content(interaction, self.original_interaction, new_page, self.pages, **self.send_args)
-
- @row.button(label='Next >')
- async def handle_next_button(self, interaction: Interaction, button: Button) -> None:
- new_page = min(len(self.pages) - 1, self.current_page + 1)
- await update_paged_content(interaction, self.original_interaction, new_page, self.pages, **self.send_args)
|