Last active
December 26, 2024 15:10
-
-
Save InterStella0/454cc51e05e60e63b81ea2e8490ef140 to your computer and use it in GitHub Desktop.
Revisions
-
InterStella0 revised this gist
May 18, 2022 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -175,7 +175,7 @@ class MyMenuPages(menus.MenuPages, inherit_buttons=False): async def go_to_previous_page(self, payload): await self.show_checked_page(self.current_page - 1) @button('<:next_check:754948796361736213>', position=Last(1)) async def go_to_next_page(self, payload): await self.show_checked_page(self.current_page + 1) -
InterStella0 revised this gist
Dec 12, 2021 . 1 changed file with 1 addition and 0 deletions.There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,7 @@ # Pagination Walkthrough in discord.py In this tutorial I will go through the entire length of pagination in discord.py I kindly ask you to go through the entire thing, as it is **not recommended** to skip the subtopics because they are interconnected in some way. ## Table Content - [Getting Started](#start) - [Menu](#menu) -
InterStella0 revised this gist
Dec 12, 2021 . 1 changed file with 3 additions and 0 deletions.There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -223,6 +223,9 @@ This ensure that there isn't an out of bound exception. 7. We would use `MyMenuPages` instead of `MenuPages` because we want to try the custom emoji. 8. The rest of the explanation are available at [Menu](#menu) As a reference, here are the emojis I'm using currently. ![emojis](https://cdn.discordapp.com/attachments/652696440396840963/919296978481975446/unknown.png) #### Output ![custom_menupages](https://cdn.discordapp.com/attachments/777501555687292928/919262731675250698/ezgif.com-gif-maker_2.gif) -
InterStella0 created this gist
Dec 11, 2021 .There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,393 @@ # Pagination Walkthrough in discord.py In this tutorial I will go through the entire length of pagination in discord.py ## Table Content - [Getting Started](#start) - [Menu](#menu) - [Pagination](#pag) - [MenuPages and ListPageSource](#menupages_list) - [MenuPages](#menupages) - [ListPageSource](#list) - [Combine MenuPages & ListPageSource](#cmenupages_list) - [Custom MenuPages](#cmenupages) - [View](#view) - [Brief explanation of View](#brief_view) - [Pagination with View](#pag_view) - [The end](#the_end) Pagination is an extremely common thing to do in discord.py that I decided to create this gist. One of the most common uses of pagination is when you want to show contents that are more than the limit of what discord allows you. In this tutorial, you would need to install `discord-ext-menus` library that was written by Danny. The creator of discord.py. This library mainly uses reactions as interfaces. Now don't worry, we will go into discord button once we fully understand this library. Before diving any further, make sure to install [discord-ext-menus](https://github.com/Rapptz/discord-ext-menus). # <a name="start"></a> Getting Started While the library contains more than just pagination. We will mainly go into pagination because that's the focus. The library contains a few classes, mainly these ones. 1. `menus.Menu` 2. `menus.MenuPages` 3. `menus.ListPageSource` 4. `menus.GroupByPageSource` 5. `menus.AsyncIteratorPageSource` However, we will only look into 3 of them, which is `menus.Menu` , `menus.MenuPages` and `menus.ListPageSource`. The rest are derived from `menus.MenuPages` which you can learn on your own after this tutorial. ## Menu <a name="menu"></a> This class is responsible for handling the reactions given and the behaviour of what a reaction would do. Let's use the example given by the library. ```python from discord.ext import menus class MyMenu(menus.Menu): async def send_initial_message(self, ctx, channel): return await channel.send(f'Hello {ctx.author}') @menus.button('\N{THUMBS UP SIGN}') async def on_thumbs_up(self, payload): await self.message.edit(content=f'Thanks {self.ctx.author}!') @menus.button('\N{THUMBS DOWN SIGN}') async def on_thumbs_down(self, payload): await self.message.edit(content=f"That's not nice {self.ctx.author}...") @menus.button('\N{BLACK SQUARE FOR STOP}\ufe0f') async def on_stop(self, payload): self.stop() @bot.command() async def your_command(ctx): menu = MyMenu() await menu.start(ctx) ``` #### Explanation 1. `send_initial_message` method is called when you call `menu.start`. You're required to return a message object for `MyMenu` to handle them. 2. `menus.button` refers to your reaction. They are automatically reacted by your bot as soon as `menu.start` is called. The first argument would be the emoji that you would want to listen to and added to the Message. 3. The callbacks of each decorator of `menus.button` are called when you reacted to the corresponding reaction in the decorator. 4. `self.stop` method stops the `MyMenu` class from listening to the reactions. This fully stops the instance from operating. To simply, this sums up on what `menus.Menu` class does. Of course, it's a lot more complicated than this but this is the simplification i can give you. ![menu_works](https://cdn.discordapp.com/attachments/777501555687292928/919237576597078096/Blank_diagram.png) #### Output ![menu_output](https://cdn.discordapp.com/attachments/777501555687292928/919238127862824960/ezgif.com-gif-maker.gif) #### Discussion As you can see, you can do plenty of things with this class. There's lots of application that can be used from this class alone. For example, you can do a confirmation button using pure reactions. Have a greater control in controlling reactions without any headache. You can also use this for controls in a game where it is purely based on reactions. While yes, discord button exist. This were made before the existence of discord buttons. It's great to have a fundamental understanding of how this will help us on creating a pagination based on reactions AND discord buttons. # Pagination <a name="pag"></a> ## MenuPages and ListPageSource <a name="menupages_list"></a> _[Reaction based pagination]_ ### MenuPages <a name="menupages"></a> This class is responsible for controlling the reactions for pagination. It is responsible for handling the reactions properly on what to do whether to show the first page, next page and so on. This class inherits `menus.Menu`. Meaning, all functionality coming from `menus.Menu` is also available in this class. This class fully has all the methods to control a pagination. However, this class alone is entirely useless. To make it useful, we will need `ListPageSource`. ### ListPageSource <a name="list"></a> This class is responsible for formatting each page in our paginator. It is also responsible for handling each of our data. Given an iterable into the class would allow it to fully handle the data into each pages. We will see this in action in [Combine MenuPages & ListPageSource](#cmenupages_list) ### Combine MenuPages & ListPageSource <a name="cmenupages_list"></a> With the help of `MenuPages` as our reaction manager and `ListPageSource` as our data and format manager. We can fully make an operational paginator with them. #### Example ```python from discord.ext import menus class MySource(menus.ListPageSource): async def format_page(self, menu, entries): return f"This is number {entries}." @bot.command() async def your_command(ctx): data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] formatter = MySource(data, per_page=1) menu = menus.MenuPages(formatter) await menu.start(ctx) ``` #### Explanation 1. `MySource` acts as a formatter, given `data`, elements will be separated given by `per_page` kwargs. In this case, each element is separated as a single page. 2. `MenuPages` accepts any class that subclasses `menus.PageSource`. This includes `ListPageSource`(our `MySource`). 3. `MenuPages` adds all the necessary reactions for navigation. 4. everytime `MenuPages` receives a reaction, it processes them on which way to go. After that, it calls `format_page` after it processed on which page to show. 5. When `format_page` is called. `menu` would be `MenuPages` instance and `entries` will be the values that were separated given by `per_page` from `data`. 6. Anything that is returned in `format_page` are displayed onto your Message. The value that can be returned are `Embed`/`dict`/`str`. #### Output ![menupages](https://cdn.discordapp.com/attachments/652696440396840963/919248946260488222/ezgif.com-gif-maker_1.gif) ### Custom MenuPages <a name="cmenupages"></a> If you want to customize the reaction. You would need to override `MenuPages` class. As we know, `MenuPages` is the class that handles the reaction. We can fully change this to fit our needs. #### Example ```python import discord from discord.ext import menus from discord.ext.menus import button, First, Last class MyMenuPages(menus.MenuPages, inherit_buttons=False): @button('<:before_fast_check:754948796139569224>', position=First(0)) async def go_to_first_page(self, payload): await self.show_page(0) @button('<:before_check:754948796487565332>', position=First(1)) async def go_to_previous_page(self, payload): await self.show_checked_page(self.current_page - 1) @button('<:next_check:754948796361736213>', position=Last(1) async def go_to_next_page(self, payload): await self.show_checked_page(self.current_page + 1) @button('<:next_fast_check:754948796391227442>', position=Last(2)) async def go_to_last_page(self, payload): max_pages = self._source.get_max_pages() last_page = max(max_pages - 1, 0) await self.show_page(last_page) @button('<:stop_check:754948796365930517>', position=Last(0)) async def stop_pages(self, payload): self.stop() class MySource(menus.ListPageSource): async def format_page(self, menu, entries): embed = discord.Embed( description=f"This is number {entries}.", color=discord.Colour.random() ) embed.set_footer(text=f"Requested by {menu.ctx.author}") return embed @bot.command() async def your_command(ctx): data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] formatter = MySource(data, per_page=1) menu = MyMenuPages(formatter) await menu.start(ctx) ``` #### Explanation 1. Setting `inherit_buttons` kwargs to `False` removes buttons that came from `MenuPages`. 2. in each `button` decorator, the first argument would be your emoji. Followed by `position` kwargs. `position` kwargs accepts `Position`, `First`, `Last` class. 3. `First` refers to your position of button. This class inherits `Position`. `First` acts as an anchor, where it will always be added before `Last` class gets added. In this case, the position of buttons would be; - `First(0)`, `First(1)`, `Last(0)`, `Last(1)`, `Last(2)` 4. `show_page` method sets the `current_page` to your page. In this case, it is used in `go_to_first_page` where we only wanna show the first page, and `go_to_last_page` where we only want to show the last page. 5. `show_checked_page` method uses `show_page`, but checks if it is within the range of your page. This ensure that there isn't an out of bound exception. 6. `format_page` this time returns an embed, with a randomized color to make it pretty. 7. We would use `MyMenuPages` instead of `MenuPages` because we want to try the custom emoji. 8. The rest of the explanation are available at [Menu](#menu) #### Output ![custom_menupages](https://cdn.discordapp.com/attachments/777501555687292928/919262731675250698/ezgif.com-gif-maker_2.gif) #### Discussion As you can see, it is relatively easy to create your own pagination with the default MenuPages. The complexity increases as you want to start doing customization, however this can be easy once you get the hang of it. It is recommended for you to explore other classes for other use cases. However, I'm not gonna be focusing them here. Now that we have the fundamentals of menus. We can also construct them using View on our own ways. ## View <a name="view"></a> _[Button based pagination]_ This tutorial is only compatible with discord.py 2.0. Any lower such as discord.py 1.7 will not have this class because it was not built with the newer feature of discord such as buttons and dropdowns. You would need to install discord.py 2.0 via git instead of pypi. 2.0 were never released thus not available in pypi. ### Brief explanation of View <a name="brief_view"></a> This class is responsible for handling classes that inherits from [discord.ui.Item](https://discordpy.readthedocs.io/en/master/api.html#discord.ui.Item). This includes [discord.ui.Button](https://discordpy.readthedocs.io/en/master/api.html#discord.ui.Button) which we will be using in this tutorial as navigation for the pagination. #### Classes relating to View Here are a list of classes that is related to View. 1. `discord.ui.View` 2. `discord.ui.Item` 3. `discord.ui.Button` 4. `discord.ui.Select` However, we will only talk about 2 here which is `discord.ui.View` and `discord.ui.Button`. Those are the only thing we need for the pagination. To start, `discord.ui.View` works similarly like `menus.Menu`. To demonstrate, here's an example. ```python import discord from discord import ui class MyView(ui.View): @ui.button(label="Hello", emoji="\U0001f590", style=discord.ButtonStyle.blurple) async def on_click_hello(self, button, interaction): await interaction.response.send_message("Hi") @bot.command() async def your_command(ctx): view = MyView() await ctx.send("Click button", view=view) ``` #### Explanation 1. `ui.button` is a decorator that will create a Button. The parameter are almost the same as [discord.ui.Button](https://discordpy.readthedocs.io/en/master/api.html#discord.ui.Button). 2. The said decorator will call the callback when clicked which in this case refers to `on_click_hello` method. It will pass `button`which is `discord.ui.Button` and `interaction` which is `discord.Interaction` as the parameter. 3. `interaction.response` returns [`InteractionResponse`](https://discordpy.readthedocs.io/en/master/api.html#discord.InteractionResponse) instance, which you can use to respond to the user. We will use `send_message` for now to send a message. 4. `MyView` is instantiated 5. `abc.Messageable.send` contains `view` kwargs which you can pass your `View` instance into. In our case, `view` is the instance of `MyView`. #### Output ![view_button](https://cdn.discordapp.com/attachments/777501555687292928/919268448708730930/ezgif.com-gif-maker_3.gif) ### Pagination with View <a name="pag_view"></a> With this brief knowledge, we can now make pagination with View. As we know, `MenuPages` acts as the navigation of the pagination while `ListPageSource` acts as the formatter which format the data and the page. While `menus.Menu` handles reactions and `menus.MenuPages` contains everything about pagination handling that we really needed. We only need the handling part. Based on this knowledge alone, we can combine `MenuPages` with `discord.ui.View` to make a fully functioning paginator with `discord.ui.Button` as the navigation. This code will be similar to [Custom MenuPages](#cmenupages) code. #### Example ```python import discord from discord import ui from discord.ext import menus class MyMenuPages(ui.View, menus.MenuPages): def __init__(self, source): super().__init__(timeout=60) self._source = source self.current_page = 0 self.ctx = None self.message = None async def start(self, ctx, *, channel=None, wait=False): # We wont be using wait/channel, you can implement them yourself. This is to match the MenuPages signature. await self._source._prepare_once() self.ctx = ctx self.message = await self.send_initial_message(ctx, ctx.channel) async def _get_kwargs_from_page(self, page): """This method calls ListPageSource.format_page class""" value = await super()._get_kwargs_from_page(page) if 'view' not in value: value.update({'view': self}) return value async def interaction_check(self, interaction): """Only allow the author that invoke the command to be able to use the interaction""" return interaction.user == self.ctx.author # This is extremely similar to Custom MenuPages(I will not explain these) @ui.button(emoji='<:before_fast_check:754948796139569224>', style=discord.ButtonStyle.blurple) async def first_page(self, button, interaction): await self.show_page(0) @ui.button(emoji='<:before_check:754948796487565332>', style=discord.ButtonStyle.blurple) async def before_page(self, button, interaction): await self.show_checked_page(self.current_page - 1) @ui.button(emoji='<:stop_check:754948796365930517>', style=discord.ButtonStyle.blurple) async def stop_page(self, button, interaction): self.stop() @ui.button(emoji='<:next_check:754948796361736213>', style=discord.ButtonStyle.blurple) async def next_page(self, button, interaction): await self.show_checked_page(self.current_page + 1) @ui.button(emoji='<:next_fast_check:754948796391227442>', style=discord.ButtonStyle.blurple) async def last_page(self, button, interaction): await self.show_page(self._source.get_max_pages() - 1) @bot.command() async def your_command(ctx): data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] formatter = MySource(data, per_page=1) # MySource came from Custom MenuPages subtopic. [Please refer to that] menu = MyMenuPages(formatter) await menu.start(ctx) ``` _Jesus christ that's a long code_ #### Explanation 1. We inherit both `ui.View` and `menus.MenuPages`. Where we will borrow methods from `menus.MenuPages` while using `ui.View` as our main navigation for the pagination. 2. In `__init__`, the `super().__init__` will refer to `ui.View` instead. To understand why this is, learn Method Resolution Order(MRO). `source` argument must be a `PageSource` instance. The rest of the attribute are required to be assign because `MenuPages` will use them. 3. In `start` method, it will follow the `MenuPages` method signature. `self.send_initial_message` is a method from `MenuPages`, it acts as sending a message to the user and returns a Message object. We will store them in `self.message` 4. `self._source._prepare_once` is a method to declare that the `PageSource` object has started. 5. `_get_kwargs_from_page` method is also from `MenuPages`, it is responsible for calling `format_page` and returns a dictionary which will directly be used in `discord.Message.edit` kwargs. We take advantage of this and put `view` as the to put our `View` object into the message for navigation. 6. `interaction_check` is a method of `ui.View`. It gets called when a button is clicked. Return `True` to allow for callback to be called. We will use `interaction.user` where it's the person who clicked the button to check if they are our author. If it is, it's True else return False. Optionally, you would send an ephemaral message to the user if it is not the author. 7. The rest of the explanation must refer to [Custom MenuPages](#cmenupages) subtopic. It is exactly the same explanation. #### Output ![view_pagination](https://cdn.discordapp.com/attachments/777501555687292928/919281737446608916/ezgif.com-gif-maker_4.gif) #### Discussion While yes it may seem long. But keep in mind, you would only do `MyMenuPages` once, after that, you can create infinite amount of `ListPageSource` that fit your need for all of the pagination you will ever need. Feel free to derive this code into a much more advance handler. I've only talked briefly on how to use `ui.View`. There's plenty more uses you can do with it and I would recommend exploring more on `ui.View`. Danny has created plenty of View examples here: [Click Here](https://github.com/Rapptz/discord.py/tree/master/examples/views). # The end <a name="the_end"></a> Congrats, you've reached the end of this tutorial. I don't recommend skipping any of the topic here because you will be left confused on what you've just read and learnt nothing. Each subtopic is linked to each other hence why. Anyways, any question regarding `ui.View` and `discord-ext-menus` can be asked in discord.py. I don't check this gist that much. So your question wont be answered that quickly. Tbh i wrote this because i need to remind myself about this in about a year. that's all, thanks.