From 7228ef64a58f1b41576b12bb3e72db1c751919f3 Mon Sep 17 00:00:00 2001 From: Maxikinz <57434374+ImAnAgent@users.noreply.github.com> Date: Sat, 9 Oct 2021 19:03:16 +0200 Subject: [PATCH 1/7] Create a working button paginator Simple Embed paginator that I've been using for a while. Thought it could help others too. --- examples/views/paginator.py | 105 ++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 examples/views/paginator.py diff --git a/examples/views/paginator.py b/examples/views/paginator.py new file mode 100644 index 00000000..dfe3cfdc --- /dev/null +++ b/examples/views/paginator.py @@ -0,0 +1,105 @@ +#First we define 3 View subclasses that we will switch between depending on if we are viewing the first page, a page in the middle, or the last page. +#The first page has the "left" button disabled, because there is no left page to go to... +class FirstPageComps(View): + def __init__(self, author_id: int): + super().__init__() + self.value = None + self.author_id = author_id + + async def interaction_check(self, interaction: Interaction): + return interaction.user.id == self.author_id + + @discord.ui.button(style=ButtonStyle.primary, disabled=True, label='Left') + async def left(self, button: discord.ui.Button, interaction: Interaction): + self.value = "left" + self.stop() + + @discord.ui.button(style=ButtonStyle.primary, label='Right') + async def right(self, button: discord.ui.Button, interaction: Interaction): + self.value = "right" + self.stop() + +#The middle pages have both left and right buttons available +class MiddlePageComps(View): + def __init__(self, author_id: int): + super().__init__() + self.value = None + self.author_id = author_id + + async def interaction_check(self, interaction: Interaction): + return interaction.user.id == self.author_id + + @discord.ui.button(style=ButtonStyle.primary, label='Left') + async def left(self, button: discord.ui.Button, interaction: Interaction): + self.value = "left" + self.stop() + + @discord.ui.button(style=ButtonStyle.primary, label='Right') + async def right(self, button: discord.ui.Button, interaction: Interaction): + self.value = "right" + self.stop() + +#The last page has the right button disabled, because there's no right page to go to anymore... +class LastPageComps(View): + def __init__(self, author_id: int): + super().__init__() + self.value = None + self.author_id = author_id + + async def interaction_check(self, interaction: Interaction): + return interaction.user.id == self.author_id + + @discord.ui.button(style=ButtonStyle.primary, label='Left') + async def left(self, button: discord.ui.Button, interaction: Interaction): + self.value = "left" + self.stop() + + @discord.ui.button(style=ButtonStyle.primary, label='Right', disabled=True) + async def right(self, button: discord.ui.Button, interaction: Interaction): + self.value = "right" + self.stop() + + +#Now we define the function that will take care of the pagination for us. It will take a list of Embeds and allow the user to cycle between them using left/right buttons +#There is also an optional title parameter in case you want to give your paginator a title, it will display in the embed title, for example if the title is Book +#then the title will look like "Book | Page 1/2". This is very optional and can be removed +async def paginate(ctx, pages: list, title: str = None): + + total_pages = len(pages) + first_page = pages[0] + last_page = pages[-1] + current_page = first_page + index = 0 + + embed = first_page + if title: + embed.title = f'{title} | Page {index+1}/{total_pages}' + + view = FirstPageComps(ctx.author.id) + #Here we send the message with the view of the first page and the FirstPageComps buttons + msg = await ctx.send(embed=embed, view=view) + + #The default timeout for Views is 3 minutes, but if a user selects to scroll between pages the timer will be reset. + #If the timer reaches 0, though, the loop will just silently stop. + while True: + wait = await view.wait() + if wait: + return + + if view.value == "right": + index += 1 + current_page = pages[index] + view = MiddlePageComps(ctx.author.id) if current_page != last_page else LastPageComps(ctx.author.id) + embed = current_page + if title: + embed.title = f'{title} | Page {index+1}/{total_pages}' + + elif view.value == "left": + index -= 1 + current_page = pages[index] + view = MiddlePageComps(ctx.author.id) if current_page != first_page else FirstPageComps(ctx.author.id) + embed = current_page + if title: + embed.title = f'{title} | Page {index+1}/{total_pages}' + + await msg.edit(embed=embed, view=view) -- 2.47.2 From c74fd90a9ce0e0fc28cdd3f5681d2b138ba3c3ae Mon Sep 17 00:00:00 2001 From: Maxikinz <57434374+ImAnAgent@users.noreply.github.com> Date: Sat, 9 Oct 2021 20:29:00 +0200 Subject: [PATCH 2/7] Update paginator.py --- examples/views/paginator.py | 66 +++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/examples/views/paginator.py b/examples/views/paginator.py index dfe3cfdc..d4511997 100644 --- a/examples/views/paginator.py +++ b/examples/views/paginator.py @@ -1,5 +1,5 @@ -#First we define 3 View subclasses that we will switch between depending on if we are viewing the first page, a page in the middle, or the last page. -#The first page has the "left" button disabled, because there is no left page to go to... +# First we define 3 View subclasses that we will switch between depending on if we are viewing the first page, a page in the middle, or the last page. +# The first page has the "left" button disabled, because there is no left page to go to... class FirstPageComps(View): def __init__(self, author_id: int): super().__init__() @@ -9,17 +9,18 @@ class FirstPageComps(View): async def interaction_check(self, interaction: Interaction): return interaction.user.id == self.author_id - @discord.ui.button(style=ButtonStyle.primary, disabled=True, label='Left') + @discord.ui.button(style=ButtonStyle.primary, disabled=True, label="Left") async def left(self, button: discord.ui.Button, interaction: Interaction): self.value = "left" self.stop() - @discord.ui.button(style=ButtonStyle.primary, label='Right') + @discord.ui.button(style=ButtonStyle.primary, label="Right") async def right(self, button: discord.ui.Button, interaction: Interaction): self.value = "right" self.stop() - -#The middle pages have both left and right buttons available + + +# The middle pages have both left and right buttons available class MiddlePageComps(View): def __init__(self, author_id: int): super().__init__() @@ -29,17 +30,18 @@ class MiddlePageComps(View): async def interaction_check(self, interaction: Interaction): return interaction.user.id == self.author_id - @discord.ui.button(style=ButtonStyle.primary, label='Left') + @discord.ui.button(style=ButtonStyle.primary, label="Left") async def left(self, button: discord.ui.Button, interaction: Interaction): self.value = "left" self.stop() - @discord.ui.button(style=ButtonStyle.primary, label='Right') + @discord.ui.button(style=ButtonStyle.primary, label="Right") async def right(self, button: discord.ui.Button, interaction: Interaction): self.value = "right" self.stop() -#The last page has the right button disabled, because there's no right page to go to anymore... + +# The last page has the right button disabled, because there's no right page to go to anymore... class LastPageComps(View): def __init__(self, author_id: int): super().__init__() @@ -49,20 +51,20 @@ class LastPageComps(View): async def interaction_check(self, interaction: Interaction): return interaction.user.id == self.author_id - @discord.ui.button(style=ButtonStyle.primary, label='Left') + @discord.ui.button(style=ButtonStyle.primary, label="Left") async def left(self, button: discord.ui.Button, interaction: Interaction): self.value = "left" self.stop() - @discord.ui.button(style=ButtonStyle.primary, label='Right', disabled=True) + @discord.ui.button(style=ButtonStyle.primary, label="Right", disabled=True) async def right(self, button: discord.ui.Button, interaction: Interaction): self.value = "right" self.stop() - -#Now we define the function that will take care of the pagination for us. It will take a list of Embeds and allow the user to cycle between them using left/right buttons -#There is also an optional title parameter in case you want to give your paginator a title, it will display in the embed title, for example if the title is Book -#then the title will look like "Book | Page 1/2". This is very optional and can be removed + +# Now we define the function that will take care of the pagination for us. It will take a list of Embeds and allow the user to cycle between them using left/right buttons +# There is also an optional title parameter in case you want to give your paginator a title, it will display in the embed title, for example if the title is Book +# then the title will look like "Book | Page 1/2". This is very optional and can be removed async def paginate(ctx, pages: list, title: str = None): total_pages = len(pages) @@ -73,33 +75,41 @@ async def paginate(ctx, pages: list, title: str = None): embed = first_page if title: - embed.title = f'{title} | Page {index+1}/{total_pages}' - + embed.title = f"{title} | Page {index+1}/{total_pages}" + view = FirstPageComps(ctx.author.id) - #Here we send the message with the view of the first page and the FirstPageComps buttons + # Here we send the message with the view of the first page and the FirstPageComps buttons msg = await ctx.send(embed=embed, view=view) - - #The default timeout for Views is 3 minutes, but if a user selects to scroll between pages the timer will be reset. - #If the timer reaches 0, though, the loop will just silently stop. + + # The default timeout for Views is 3 minutes, but if a user selects to scroll between pages the timer will be reset. + # If the timer reaches 0, though, the loop will just silently stop. while True: wait = await view.wait() if wait: return - + if view.value == "right": index += 1 current_page = pages[index] - view = MiddlePageComps(ctx.author.id) if current_page != last_page else LastPageComps(ctx.author.id) + view = ( + MiddlePageComps(ctx.author.id) + if current_page != last_page + else LastPageComps(ctx.author.id) + ) embed = current_page if title: - embed.title = f'{title} | Page {index+1}/{total_pages}' - + embed.title = f"{title} | Page {index+1}/{total_pages}" + elif view.value == "left": index -= 1 current_page = pages[index] - view = MiddlePageComps(ctx.author.id) if current_page != first_page else FirstPageComps(ctx.author.id) + view = ( + MiddlePageComps(ctx.author.id) + if current_page != first_page + else FirstPageComps(ctx.author.id) + ) embed = current_page if title: - embed.title = f'{title} | Page {index+1}/{total_pages}' - + embed.title = f"{title} | Page {index+1}/{total_pages}" + await msg.edit(embed=embed, view=view) -- 2.47.2 From fae85d072feb61ef018854d9954f631c8d9262d4 Mon Sep 17 00:00:00 2001 From: Maxikinz <57434374+ImAnAgent@users.noreply.github.com> Date: Sat, 9 Oct 2021 20:37:26 +0200 Subject: [PATCH 3/7] Black paginator.py with 120 char limit per line --- examples/views/paginator.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/examples/views/paginator.py b/examples/views/paginator.py index d4511997..6bddec04 100644 --- a/examples/views/paginator.py +++ b/examples/views/paginator.py @@ -91,11 +91,7 @@ async def paginate(ctx, pages: list, title: str = None): if view.value == "right": index += 1 current_page = pages[index] - view = ( - MiddlePageComps(ctx.author.id) - if current_page != last_page - else LastPageComps(ctx.author.id) - ) + view = MiddlePageComps(ctx.author.id) if current_page != last_page else LastPageComps(ctx.author.id) embed = current_page if title: embed.title = f"{title} | Page {index+1}/{total_pages}" @@ -103,11 +99,7 @@ async def paginate(ctx, pages: list, title: str = None): elif view.value == "left": index -= 1 current_page = pages[index] - view = ( - MiddlePageComps(ctx.author.id) - if current_page != first_page - else FirstPageComps(ctx.author.id) - ) + view = MiddlePageComps(ctx.author.id) if current_page != first_page else FirstPageComps(ctx.author.id) embed = current_page if title: embed.title = f"{title} | Page {index+1}/{total_pages}" -- 2.47.2 From cb782ce3d844e7aed85a6b6d49081ed6fabed3e8 Mon Sep 17 00:00:00 2001 From: Maxikinz <57434374+ImAnAgent@users.noreply.github.com> Date: Sat, 9 Oct 2021 20:40:44 +0200 Subject: [PATCH 4/7] Update paginator.py --- examples/views/paginator.py | 114 +++++++++++++++++++++++++++++------- 1 file changed, 93 insertions(+), 21 deletions(-) diff --git a/examples/views/paginator.py b/examples/views/paginator.py index 6bddec04..061e2cf8 100644 --- a/examples/views/paginator.py +++ b/examples/views/paginator.py @@ -1,63 +1,125 @@ # First we define 3 View subclasses that we will switch between depending on if we are viewing the first page, a page in the middle, or the last page. # The first page has the "left" button disabled, because there is no left page to go to... class FirstPageComps(View): - def __init__(self, author_id: int): + def __init__( + self, + author_id: int, + ): super().__init__() self.value = None self.author_id = author_id - async def interaction_check(self, interaction: Interaction): + async def interaction_check( + self, + interaction: Interaction, + ): return interaction.user.id == self.author_id - @discord.ui.button(style=ButtonStyle.primary, disabled=True, label="Left") - async def left(self, button: discord.ui.Button, interaction: Interaction): + @discord.ui.button( + style=ButtonStyle.primary, + disabled=True, + label="Left", + ) + async def left( + self, + button: discord.ui.Button, + interaction: Interaction, + ): self.value = "left" self.stop() - @discord.ui.button(style=ButtonStyle.primary, label="Right") - async def right(self, button: discord.ui.Button, interaction: Interaction): + @discord.ui.button( + style=ButtonStyle.primary, + label="Right", + ) + async def right( + self, + button: discord.ui.Button, + interaction: Interaction, + ): self.value = "right" self.stop() # The middle pages have both left and right buttons available class MiddlePageComps(View): - def __init__(self, author_id: int): + def __init__( + self, + author_id: int, + ): super().__init__() self.value = None self.author_id = author_id - async def interaction_check(self, interaction: Interaction): + async def interaction_check( + self, + interaction: Interaction, + ): return interaction.user.id == self.author_id - @discord.ui.button(style=ButtonStyle.primary, label="Left") - async def left(self, button: discord.ui.Button, interaction: Interaction): + @discord.ui.button( + style=ButtonStyle.primary, + label="Left", + ) + async def left( + self, + button: discord.ui.Button, + interaction: Interaction, + ): self.value = "left" self.stop() - @discord.ui.button(style=ButtonStyle.primary, label="Right") - async def right(self, button: discord.ui.Button, interaction: Interaction): + @discord.ui.button( + style=ButtonStyle.primary, + label="Right", + ) + async def right( + self, + button: discord.ui.Button, + interaction: Interaction, + ): self.value = "right" self.stop() # The last page has the right button disabled, because there's no right page to go to anymore... class LastPageComps(View): - def __init__(self, author_id: int): + def __init__( + self, + author_id: int, + ): super().__init__() self.value = None self.author_id = author_id - async def interaction_check(self, interaction: Interaction): + async def interaction_check( + self, + interaction: Interaction, + ): return interaction.user.id == self.author_id - @discord.ui.button(style=ButtonStyle.primary, label="Left") - async def left(self, button: discord.ui.Button, interaction: Interaction): + @discord.ui.button( + style=ButtonStyle.primary, + label="Left", + ) + async def left( + self, + button: discord.ui.Button, + interaction: Interaction, + ): self.value = "left" self.stop() - @discord.ui.button(style=ButtonStyle.primary, label="Right", disabled=True) - async def right(self, button: discord.ui.Button, interaction: Interaction): + @discord.ui.button( + style=ButtonStyle.primary, + label="Right", + disabled=True, + ) + async def right( + self, + button: discord.ui.Button, + interaction: Interaction, + ): self.value = "right" self.stop() @@ -65,7 +127,11 @@ class LastPageComps(View): # Now we define the function that will take care of the pagination for us. It will take a list of Embeds and allow the user to cycle between them using left/right buttons # There is also an optional title parameter in case you want to give your paginator a title, it will display in the embed title, for example if the title is Book # then the title will look like "Book | Page 1/2". This is very optional and can be removed -async def paginate(ctx, pages: list, title: str = None): +async def paginate( + ctx, + pages: list, + title: str = None, +): total_pages = len(pages) first_page = pages[0] @@ -79,7 +145,10 @@ async def paginate(ctx, pages: list, title: str = None): view = FirstPageComps(ctx.author.id) # Here we send the message with the view of the first page and the FirstPageComps buttons - msg = await ctx.send(embed=embed, view=view) + msg = await ctx.send( + embed=embed, + view=view, + ) # The default timeout for Views is 3 minutes, but if a user selects to scroll between pages the timer will be reset. # If the timer reaches 0, though, the loop will just silently stop. @@ -104,4 +173,7 @@ async def paginate(ctx, pages: list, title: str = None): if title: embed.title = f"{title} | Page {index+1}/{total_pages}" - await msg.edit(embed=embed, view=view) + await msg.edit( + embed=embed, + view=view, + ) -- 2.47.2 From d06bfce837939debf9112ec48c652d9955464d67 Mon Sep 17 00:00:00 2001 From: Maxikinz <57434374+ImAnAgent@users.noreply.github.com> Date: Sat, 9 Oct 2021 20:57:02 +0200 Subject: [PATCH 5/7] Include minimal working example --- examples/views/paginator.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/examples/views/paginator.py b/examples/views/paginator.py index 061e2cf8..21460bb7 100644 --- a/examples/views/paginator.py +++ b/examples/views/paginator.py @@ -1,5 +1,22 @@ -# First we define 3 View subclasses that we will switch between depending on if we are viewing the first page, a page in the middle, or the last page. -# The first page has the "left" button disabled, because there is no left page to go to... +import discord +from discord.ext import commands +from discord import ButtonStyle, Embed, Interaction +from discord.ui import Button, View + + +class Bot(commands.Bot): + def __init__(self): + super().__init__( + command_prefix=commands.when_mentioned_or("$"), intents=discord.Intents(guilds=True, messages=True) + ) + + async def on_ready(self): + print(f"Logged in as {self.user} (ID: {self.user.id})") + print("------") + + +# Define 3 View subclasses that we will switch between depending on if we are viewing the first page, a page in the middle, or the last page. +# The first page has the "left" button disabled, because there is no left page to go to class FirstPageComps(View): def __init__( self, @@ -82,7 +99,7 @@ class MiddlePageComps(View): self.stop() -# The last page has the right button disabled, because there's no right page to go to anymore... +# The last page has the right button disabled, because there's no right page to go to class LastPageComps(View): def __init__( self, @@ -177,3 +194,17 @@ async def paginate( embed=embed, view=view, ) + + +bot = Bot() + +# To test our new function, let's create a list of a couple Embeds and send +@bot.command() +async def sendpages(ctx): + page1 = Embed(description="This is page 1") + page2 = Embed(description="This is page 2") + page3 = Embed(description="This is page 3") + await paginate(ctx, [page1, page2, page3]) + + +bot.run("token") -- 2.47.2 From f7398c9acddb1d04349369a220b9142d0aeee2f1 Mon Sep 17 00:00:00 2001 From: Maxikinz <57434374+ImAnAgent@users.noreply.github.com> Date: Sat, 9 Oct 2021 21:00:58 +0200 Subject: [PATCH 6/7] Fix paginator.py typo --- examples/views/paginator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/views/paginator.py b/examples/views/paginator.py index 21460bb7..2c92dec7 100644 --- a/examples/views/paginator.py +++ b/examples/views/paginator.py @@ -198,7 +198,7 @@ async def paginate( bot = Bot() -# To test our new function, let's create a list of a couple Embeds and send +# To test our new function, let's create a list of a couple Embeds and let our paginator deal with the sending and buttons @bot.command() async def sendpages(ctx): page1 = Embed(description="This is page 1") @@ -206,5 +206,4 @@ async def sendpages(ctx): page3 = Embed(description="This is page 3") await paginate(ctx, [page1, page2, page3]) - bot.run("token") -- 2.47.2 From 32010262420e4d04359c9ee8de434d24447ad142 Mon Sep 17 00:00:00 2001 From: Maxikinz <57434374+ImAnAgent@users.noreply.github.com> Date: Sat, 9 Oct 2021 22:16:57 +0200 Subject: [PATCH 7/7] Update paginator.py --- examples/views/paginator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/views/paginator.py b/examples/views/paginator.py index 2c92dec7..0101cc0b 100644 --- a/examples/views/paginator.py +++ b/examples/views/paginator.py @@ -206,4 +206,5 @@ async def sendpages(ctx): page3 = Embed(description="This is page 3") await paginate(ctx, [page1, page2, page3]) + bot.run("token") -- 2.47.2