Skip to content

Commit 24f05ed

Browse files
authored
Merge pull request #1123 from krittick/pages-update-2
Add `Page` class to `ext.pages` to allow for greater flexibility with page contents
1 parent 085d693 commit 24f05ed

File tree

3 files changed

+117
-37
lines changed

3 files changed

+117
-37
lines changed

discord/ext/pages/pagination.py

Lines changed: 86 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"Paginator",
3232
"PageGroup",
3333
"PaginatorMenu",
34+
"Page",
3435
)
3536

3637

@@ -104,6 +105,48 @@ async def callback(self, interaction: discord.Interaction):
104105
await self.paginator.goto_page(page_number=self.paginator.current_page)
105106

106107

108+
class Page:
109+
"""Represents a page shown in the paginator.
110+
111+
Allows for directly referencing and modifying each page as a class instance.
112+
113+
Parameters
114+
----------
115+
content: :class:`str`
116+
The content of the page. Corresponds to the :class:`discord.Message.content` attribute.
117+
embeds: Optional[List[Union[List[:class:`discord.Embed`], :class:`discord.Embed`]]]
118+
The embeds of the page. Corresponds to the :class:`discord.Message.embeds` attribute.
119+
"""
120+
121+
def __init__(
122+
self, content: Optional[str] = None, embeds: Optional[List[Union[List[discord.Embed], discord.Embed]]] = None
123+
):
124+
if content is None and embeds is None:
125+
raise discord.InvalidArgument("A page cannot have both content and embeds equal to None.")
126+
self._content = content
127+
self._embeds = embeds
128+
129+
@property
130+
def content(self) -> Optional[str]:
131+
"""Gets the content for the page."""
132+
return self._content
133+
134+
@content.setter
135+
def content(self, value: Optional[str]):
136+
"""Sets the content for the page."""
137+
self._content = value
138+
139+
@property
140+
def embeds(self) -> Optional[List[Union[List[discord.Embed], discord.Embed]]]:
141+
"""Gets the embeds for the page."""
142+
return self._embeds
143+
144+
@embeds.setter
145+
def embeds(self, value: Optional[List[Union[List[discord.Embed], discord.Embed]]]):
146+
"""Sets the embeds for the page."""
147+
self._embeds = value
148+
149+
107150
class PageGroup:
108151
"""Creates a group of pages which the user can switch between.
109152
@@ -116,8 +159,8 @@ class PageGroup:
116159
117160
Parameters
118161
----------
119-
pages: Union[List[:class:`str`], List[Union[List[:class:`discord.Embed`], :class:`discord.Embed]]]
120-
The list of strings, embeds, or list of embeds to include in the page group.
162+
pages: Union[List[:class:`str`], List[:class:`Page`], List[Union[List[:class:`discord.Embed`], :class:`discord.Embed]]]
163+
The list of :class:`Page` objects, strings, embeds, or list of embeds to include in the page group.
121164
label: :class:`str`
122165
The label shown on the corresponding PaginatorMenu dropdown option.
123166
Also used as the SelectOption value.
@@ -150,7 +193,7 @@ class PageGroup:
150193

151194
def __init__(
152195
self,
153-
pages: Union[List[str], List[Union[List[discord.Embed], discord.Embed]]],
196+
pages: Union[List[str], List[Page], List[Union[List[discord.Embed], discord.Embed]]],
154197
label: str,
155198
description: str,
156199
emoji: Union[str, discord.Emoji, discord.PartialEmoji] = None,
@@ -186,8 +229,8 @@ class Paginator(discord.ui.View):
186229
187230
Parameters
188231
----------
189-
pages: Union[List[:class:`PageGroup`], List[:class:`str`], List[Union[List[:class:`discord.Embed`], :class:`discord.Embed`]]]
190-
The list of :class:`PageGroup` objects, strings, embeds, or list of embeds to paginate.
232+
pages: Union[List[:class:`PageGroup`], List[:class:`Page`], List[:class:`str`], List[Union[List[:class:`discord.Embed`], :class:`discord.Embed`]]]
233+
The list of :class:`PageGroup` objects, :class:`Page` objects, strings, embeds, or list of embeds to paginate.
191234
If a list of :class:`PageGroup` objects is provided and `show_menu` is ``False``, only the first page group will be displayed.
192235
show_disabled: :class:`bool`
193236
Whether to show disabled buttons.
@@ -233,7 +276,7 @@ class Paginator(discord.ui.View):
233276

234277
def __init__(
235278
self,
236-
pages: Union[List[PageGroup], List[str], List[Union[List[discord.Embed], discord.Embed]]],
279+
pages: Union[List[PageGroup], List[Page], List[str], List[Union[List[discord.Embed], discord.Embed]]],
237280
show_disabled: bool = True,
238281
show_indicator=True,
239282
show_menu=False,
@@ -248,15 +291,19 @@ def __init__(
248291
) -> None:
249292
super().__init__(timeout=timeout)
250293
self.timeout: float = timeout
251-
self.pages: Union[List[PageGroup], List[str], List[Union[List[discord.Embed], discord.Embed]]] = pages
294+
self.pages: Union[
295+
List[PageGroup], List[str], List[Page], List[Union[List[discord.Embed], discord.Embed]]
296+
] = pages
252297
self.current_page = 0
253298
self.menu: Optional[PaginatorMenu] = None
254299
self.show_menu = show_menu
255300
self.page_groups: Optional[List[PageGroup]] = None
256301

257302
if all(isinstance(pg, PageGroup) for pg in pages):
258303
self.page_groups = self.pages if show_menu else None
259-
self.pages: Union[List[str], List[Union[List[discord.Embed], discord.Embed]]] = self.page_groups[0].pages
304+
self.pages: Union[
305+
List[str], List[Page], List[Union[List[discord.Embed], discord.Embed]]
306+
] = self.page_groups[0].pages
260307

261308
self.page_count = len(self.pages) - 1
262309
self.buttons = {}
@@ -284,7 +331,7 @@ def __init__(
284331

285332
async def update(
286333
self,
287-
pages: Optional[Union[List[str], List[Union[List[discord.Embed], discord.Embed]]]] = None,
334+
pages: Optional[Union[List[str], List[Page], List[Union[List[discord.Embed], discord.Embed]]]] = None,
288335
show_disabled: Optional[bool] = None,
289336
show_indicator: Optional[bool] = None,
290337
author_check: Optional[bool] = None,
@@ -300,8 +347,8 @@ async def update(
300347
301348
Parameters
302349
----------
303-
pages: Optional[Union[List[:class:`PageGroup`], List[:class:`str`], List[Union[List[:class:`discord.Embed`], :class:`discord.Embed]]]]
304-
The list of :class:`PageGroup` objects, strings, embeds, or list of embeds to paginate.
350+
pages: Optional[Union[List[:class:`PageGroup`], List[:class:`Page`], List[:class:`str`], List[Union[List[:class:`discord.Embed`], :class:`discord.Embed]]]]
351+
The list of :class:`PageGroup` objects, :class:`Page` objects, strings, embeds, or list of embeds to paginate.
305352
show_disabled: :class:`bool`
306353
Whether to show disabled buttons.
307354
show_indicator: :class:`bool`
@@ -326,7 +373,7 @@ async def update(
326373
"""
327374

328375
# Update pages and reset current_page to 0 (default)
329-
self.pages: Union[List[PageGroup], List[str], List[Union[List[discord.Embed], discord.Embed]]] = (
376+
self.pages: Union[List[PageGroup], List[str], List[Page], List[Union[List[discord.Embed], discord.Embed]]] = (
330377
pages if pages is not None else self.pages
331378
)
332379
self.page_count = len(self.pages) - 1
@@ -361,7 +408,7 @@ async def on_timeout(self) -> None:
361408
async def disable(
362409
self,
363410
include_custom: bool = False,
364-
page: Optional[Union[str, Union[List[discord.Embed], discord.Embed]]] = None,
411+
page: Optional[Union[str, Page, Union[List[discord.Embed], discord.Embed]]] = None,
365412
) -> None:
366413
"""Stops the paginator, disabling all of its components.
367414
@@ -378,8 +425,8 @@ async def disable(
378425
item.disabled = True
379426
if page:
380427
await self.message.edit(
381-
content=page if isinstance(page, str) else None,
382-
embeds=[] if isinstance(page, str) else page,
428+
content=page.content,
429+
embeds=page.embeds,
383430
view=self,
384431
)
385432
else:
@@ -388,7 +435,7 @@ async def disable(
388435
async def cancel(
389436
self,
390437
include_custom: bool = False,
391-
page: Optional[Union[str, Union[List[discord.Embed], discord.Embed]]] = None,
438+
page: Optional[Union[str, Page, Union[List[discord.Embed], discord.Embed]]] = None,
392439
) -> None:
393440
"""Cancels the paginator, removing all of its components from the message.
394441
@@ -406,8 +453,8 @@ async def cancel(
406453
self.remove_item(item)
407454
if page:
408455
await self.message.edit(
409-
content=page if isinstance(page, str) else None,
410-
embeds=[] if isinstance(page, str) else page,
456+
content=page.content,
457+
embeds=page.embeds,
411458
view=self,
412459
)
413460
else:
@@ -439,8 +486,8 @@ async def goto_page(self, page_number=0) -> discord.Message:
439486
page = self.get_page_content(page)
440487

441488
return await self.message.edit(
442-
content=page if isinstance(page, str) else None,
443-
embeds=[] if isinstance(page, str) else page,
489+
content=page.content,
490+
embeds=page.embeds,
444491
view=self,
445492
)
446493

@@ -588,17 +635,19 @@ def update_buttons(self) -> Dict:
588635
return self.buttons
589636

590637
@staticmethod
591-
def get_page_content(page: Union[str, discord.Embed, List[discord.Embed]]):
638+
def get_page_content(page: Union[Page, str, discord.Embed, List[discord.Embed]]) -> Page:
592639
"""Returns the correct content type for a page based on its content."""
593-
if isinstance(page, discord.Embed):
594-
return [page]
640+
if isinstance(page, Page):
641+
return page
642+
elif isinstance(page, str):
643+
return Page(content=page, embeds=[])
644+
elif isinstance(page, discord.Embed):
645+
return Page(content=None, embeds=[page])
595646
elif isinstance(page, List):
596647
if all(isinstance(x, discord.Embed) for x in page):
597-
return page
648+
return Page(content=None, embeds=page)
598649
else:
599650
raise TypeError("All list items must be embeds.")
600-
elif isinstance(page, str):
601-
return page
602651

603652
async def send(
604653
self,
@@ -658,7 +707,7 @@ async def send(
658707

659708
self.update_buttons()
660709
page = self.pages[self.current_page]
661-
page = self.get_page_content(page)
710+
page_content = self.get_page_content(page)
662711

663712
self.user = ctx.author
664713

@@ -673,8 +722,8 @@ async def send(
673722
ctx = target
674723

675724
self.message = await ctx.send(
676-
content=page if isinstance(page, str) else None,
677-
embeds=[] if isinstance(page, str) else page,
725+
content=page_content.content,
726+
embeds=page_content.embeds,
678727
view=self,
679728
reference=reference,
680729
allowed_mentions=allowed_mentions,
@@ -718,31 +767,31 @@ async def respond(
718767

719768
self.update_buttons()
720769

721-
page = self.pages[self.current_page]
722-
page = self.get_page_content(page)
770+
page: Union[Page, str, discord.Embed, List[discord.Embed]] = self.pages[self.current_page]
771+
page_content: Page = self.get_page_content(page)
723772

724773
self.user = interaction.user
725774
if target:
726775
await interaction.response.send_message(target_message, ephemeral=ephemeral)
727776
self.message = await target.send(
728-
content=page if isinstance(page, str) else None,
729-
embeds=[] if isinstance(page, str) else page,
777+
content=page_content.content,
778+
embeds=page_content.embeds,
730779
view=self,
731780
)
732781
else:
733782
if interaction.response.is_done():
734783
msg = await interaction.followup.send(
735-
content=page if isinstance(page, str) else None,
736-
embeds=[] if isinstance(page, str) else page,
784+
content=page_content.content,
785+
embeds=page_content.embeds,
737786
view=self,
738787
ephemeral=ephemeral,
739788
)
740789
# convert from WebhookMessage to Message reference to bypass 15min webhook token timeout
741790
msg = await msg.channel.fetch_message(msg.id)
742791
else:
743792
msg = await interaction.response.send_message(
744-
content=page if isinstance(page, str) else None,
745-
embeds=[] if isinstance(page, str) else page,
793+
content=page_content.content,
794+
embeds=page_content.embeds,
746795
view=self,
747796
ephemeral=ephemeral,
748797
)

docs/ext/pages/index.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,14 @@ Example usage in a cog:
291291
API Reference
292292
-------------
293293

294+
Page
295+
~~~~
296+
297+
.. attributetable:: discord.ext.pages.Page
298+
299+
.. autoclass:: discord.ext.pages.Page
300+
:members:
301+
294302
Paginator
295303
~~~~~~~~~
296304

examples/views/paginator.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,23 @@ def __init__(self, bot):
3636

3737
self.even_more_pages = ["11111", "22222", "33333"]
3838

39+
self.new_pages = [
40+
pages.Page(
41+
content="Page 1 Title!",
42+
embeds=[
43+
discord.Embed(title="New Page 1 Embed Title 1!"),
44+
discord.Embed(title="New Page 1 Embed Title 2!"),
45+
],
46+
),
47+
pages.Page(
48+
content="Page 2 Title!",
49+
embeds=[
50+
discord.Embed(title="New Page 2 Embed Title 1!"),
51+
discord.Embed(title="New Page 2 Embed Title 2!"),
52+
],
53+
),
54+
]
55+
3956
def get_pages(self):
4057
return self.pages
4158

@@ -48,6 +65,12 @@ async def pagetest_default(self, ctx: discord.ApplicationContext):
4865
paginator = pages.Paginator(pages=self.get_pages())
4966
await paginator.respond(ctx.interaction, ephemeral=False)
5067

68+
@pagetest.command(name="new")
69+
async def pagetest_new(self, ctx: discord.ApplicationContext):
70+
"""Demonstrates using the paginator with the Page class."""
71+
paginator = pages.Paginator(pages=self.new_pages)
72+
await paginator.respond(ctx.interaction, ephemeral=False)
73+
5174
@pagetest.command(name="hidden")
5275
async def pagetest_hidden(self, ctx: discord.ApplicationContext):
5376
"""Demonstrates using the paginator with disabled buttons hidden."""

0 commit comments

Comments
 (0)