Experimental Discord bot written in Python
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. """
  2. Provides a means of presenting long messages by paging them. Paging requires the
  3. source message to insert `PAGE_BREAK` characters at meaningful breaks, preferably
  4. at fairly uniform intervals.
  5. """
  6. from typing import Optional
  7. from discord import Interaction
  8. from discord.ui import LayoutView, TextDisplay, ActionRow, Button
  9. from rocketbot.utils import dump_stacktrace
  10. PAGE_BREAK = '\f'
  11. def paginate(text: str) -> list[str]:
  12. """
  13. Breaks long message text into one or more pages, using page break markers as
  14. potential clean break points.
  15. """
  16. max_page_size = 2000
  17. chunks = text.split(PAGE_BREAK)
  18. pages = [ '' ]
  19. for chunk in chunks:
  20. if len(chunk) > max_page_size:
  21. raise ValueError('Help content needs more page breaks! One chunk is too big for message.')
  22. if len(pages[-1] + chunk) < max_page_size:
  23. pages[-1] += chunk
  24. else:
  25. pages.append(chunk)
  26. page_count = len(pages)
  27. if page_count == 1:
  28. return pages
  29. # Do another pass and try to even out the page lengths
  30. indices = [ i * len(chunks) // page_count for i in range(page_count + 1) ]
  31. even_pages = [
  32. ''.join(chunks[indices[i]:indices[i + 1]])
  33. for i in range(page_count)
  34. ]
  35. for page in even_pages:
  36. if len(page) > max_page_size:
  37. # We made a page too big. Give up.
  38. return pages
  39. return even_pages
  40. async def update_paged_content(
  41. interaction: Interaction,
  42. original_interaction: Optional[Interaction],
  43. current_page: int,
  44. pages: list[str],
  45. **send_args,
  46. ) -> None:
  47. """
  48. Posts and/or updates the content of a message from paged content.
  49. Parameters
  50. ----------
  51. interaction : Interaction
  52. the current interaction, either the initial one or from a button press
  53. original_interaction : Interaction
  54. the first interaction that triggered presentation of the content, or None
  55. if this already is the first interaction
  56. current_page : int
  57. page index to show (assumed to be in bounds)
  58. pages : list[str]
  59. array of page content
  60. **send_args
  61. additional arguments to pass to the send_message method
  62. """
  63. if len(pages) == 1:
  64. # No paging needed
  65. await interaction.response.send_message(
  66. pages[0],
  67. ephemeral=True,
  68. )
  69. return
  70. try:
  71. view = _PagingLayoutView(current_page, pages, original_interaction or interaction)
  72. resolved = interaction
  73. if original_interaction is not None:
  74. # We have an original interaction from the initial command and a
  75. # new one from the button press. Use the original to swap in the
  76. # new page in place, then acknowledge the new one to satisfy the
  77. # API that we didn't fail.
  78. await original_interaction.edit_original_response(
  79. view=view,
  80. )
  81. if interaction is not original_interaction:
  82. await interaction.response.defer(ephemeral=True, thinking=False)
  83. else:
  84. # Initial send
  85. await resolved.response.send_message(
  86. view=view,
  87. ephemeral=True,
  88. **send_args,
  89. )
  90. except BaseException as e:
  91. dump_stacktrace(e)
  92. class _PagingLayoutView(LayoutView):
  93. def __init__(
  94. self,
  95. current_page: int,
  96. pages: list[str],
  97. original_interaction: Optional[Interaction],
  98. **send_args,
  99. ) -> None:
  100. super().__init__()
  101. self.current_page: int = current_page
  102. self.pages: list[str] = pages
  103. self.text.content = self.pages[self.current_page] + f'\n\n_Page {self.current_page + 1} of {len(self.pages)}_'
  104. self.original_interaction = original_interaction
  105. if current_page <= 0:
  106. self.handle_prev_button.disabled = True
  107. if current_page >= len(self.pages) - 1:
  108. self.handle_next_button.disabled = True
  109. self.send_args = send_args
  110. text = TextDisplay('')
  111. row = ActionRow()
  112. @row.button(label='< Prev')
  113. async def handle_prev_button(self, interaction: Interaction, button: Button) -> None:
  114. new_page = max(0, self.current_page - 1)
  115. await update_paged_content(interaction, self.original_interaction, new_page, self.pages, **self.send_args)
  116. @row.button(label='Next >')
  117. async def handle_next_button(self, interaction: Interaction, button: Button) -> None:
  118. new_page = min(len(self.pages) - 1, self.current_page + 1)
  119. await update_paged_content(interaction, self.original_interaction, new_page, self.pages, **self.send_args)