Package: reactionmenu
How to installYou can install the latestPyPIversion of the library by doing:$ pip install reactionmenuOr the development version:$ pip install git+https://github.com/Defxult/reactionmenuIntentsMinimum intents neededbot=commands.Bot(...,intents=discord.Intents(messages=True,guilds=True,reactions=True,members=True))ReactionMenuclass reactionmenu.ReactionMenu(method: Union[Context, discord.Interaction], /, *, menu_type: MenuType, **kwargs)AReactionMenuis a menu that uses emojis which are either custom guild emojis or a normal emoji to control the pagination process. If you're not looking for any of the fancy features and just want something simple, this is the one to use.Click to show ReactionMenu documentationHow to importfromreactionmenuimportReactionMenu,ReactionButtonThis library comes with several methods and options in order to make a discord reaction menu simple. Once you have imported the proper classes, you will initialize the constructor like so:menu=ReactionMenu(method,menu_type=ReactionMenu.TypeEmbed)Parameters of the ReactionMenu constructormethod(Union[discord.ext.commands.Context, discord.Interaction]) A context or interaction objectmenu_type(MenuType) The configuration of the menuReactionMenu.TypeEmbed, a normal embed pagination menuReactionMenu.TypeEmbedDynamic, an embed pagination menu with dynamic dataReactionMenu.TypeText, a text only pagination menuKwargs of the ReactionMenu constructorNameTypeDefault ValueUsed forInfowrap_in_codeblockstrNoneReactionMenu.TypeEmbedDynamicThe discord codeblock language identifier to wrap your data in. Example:ReactionMenu(ctx, ..., wrap_in_codeblock='py')custom_embeddiscord.EmbedNoneReactionMenu.TypeEmbedDynamicEmbed object to use when adding data withReactionMenu.add_row(). Used for styling purposesdelete_on_timeoutboolFalseAll menu typesDelete the menu when it times outclear_reactions_afterboolTrueAll menu typesdelete all reactions after the menu times outnavigation_speedstrReactionMenu.NORMALAll menu typesSets if the user needs to wait for the reaction to be removed by the bot before "turning" the page. Setting the speed toReactionMenu.FASTmakes it so that there is no need to wait (reactions are not removed on each press) and can navigate lengthy menu's more quicklyonly_rolesList[discord.Role]NoneAll menu typesIf set, only members with any of the given roles are allowed to control the menu. The menu owner can always control the menutimeoutUnion[int, float, None]60.0All menu typesThe timer for when the menu times out. Can beNonefor no timeoutshow_page_directorboolTrueAll menu typesShown at the bottom of each embed page. "Page 1/20"namestrNoneAll menu typesA name you can set for the menustylestr"Page $/&"All menu typesA custom page director style you can select. "$" represents the current page, "&" represents the total amount of pages. Example:ReactionMenu(ctx, ..., style='On $ out of &')all_can_clickboolFalseAll menu typesSets if everyone is allowed to control when pages are 'turned' when buttons are clickeddelete_interactionsboolTrueAll menu typesDelete the prompt message by the bot and response message by the user when asked what page they would like to go to when usingReactionButton.Type.GO_TO_PAGErows_requestedintNoneReactionMenu.TypeEmbedDynamicThe amount of information perReactionMenu.add_row()you would like applied to each embed pageremove_extra_emojisboolFalseAll menu typesIfTrue, all emojis (reactions) added to the menu message that were not originally added to the menu will be removedPages for ReactionMenuDepending on themenu_type, pages can either be astr,discord.Embed, or a combination ofcontentandfiles(example below)If themenu_typeisReactionMenu.TypeEmbed, use embedsIf themenu_typeisReactionMenu.TypeText(text only menu) orReactionMenu.TypeEmbedDynamic(embed only menu), use strings.Associated methodsReactionMenu.add_page(embed: discord.Embed=MISSING, content: Optional[str]=None, files: Optional[Sequence[discord.File]]=None)ReactionMenu.add_pages(pages: Sequence[Union[discord.Embed, str]])ReactionMenu.add_row(data: str)ReactionMenu.remove_all_pages()ReactionMenu.clear_all_row_data()ReactionMenu.remove_page(page_number: int)ReactionMenu.set_main_pages(*embeds: Embed)ReactionMenu.set_last_pages(*embeds: Embed)Adding Pages# ReactionMenu.TypeEmbedmenu=ReactionMenu(method,menu_type=ReactionMenu.TypeEmbed)menu.add_page(summer_embed)menu.add_page(winter_embed)# ReactionMenu.TypeTextmenu=ReactionMenu(method,menu_type=ReactionMenu.TypeText)menu.add_page(content='Its so hot!')menu.add_page(content='Its so cold!')ReactionMenu.TypeTextATypeTextmenu is a text based pagination menu. No embeds are involved in the pagination process, only plain text is used.Stacked PagesWithv3.1.0+, you can paginate with more than just an embed or text. You can combine text, embeds, as well as files. But depending on themenu_typethe combination can be restricted. Here is an example of a menu with amenu_typeofTypeEmbedthat is stacked.# You can use regular commands as well@bot.tree.command(description="These are stacked pages",guild=discord.Object(id=...))asyncdefstacked(interaction:discord.Interaction):menu=ReactionMenu(interaction,menu_type=ReactionMenu.TypeEmbed)menu.add_page(discord.Embed(title="My Embed"),content="This content is stacked on top of a file",files=[discord.File("stacked.py")])menu.add_page(discord.Embed(title="Hey Wumpos, can you say hi to the person reading this? 😃"))menu.add_page(discord.Embed(title="Hi, I'm Wumpos!"),files=[discord.File("wumpos.gif")])menu.add_button(ReactionButton.back())menu.add_button(ReactionButton.next())awaitmenu.start()Since themenu_typeisTypeEmbed, there always has to be an embed on each page. If themenu_typewasTypeText, embeds aren't allowed and you will be restricted to only using thefilesparameter.ReactionMenu.TypeEmbedDynamicA dynamic menu is used when you do not know how much information will be applied to the menu. For example, if you were to request information from a database, that information can always change. You query something and you might get 1,500 results back, and the next maybe only 800. A dynamic menu pieces all this information together for you and adds it to an embed page by rows of data.ReactionMenu.add_row()is best used in some sort ofIterablewhere everything can be looped through, but only add the amount of data you want to the menu page.NOTE:In a dynamic menu, all added data is placed in the description section of an embed. If you choose to use acustom_embed, all text in the description will be overridden with the data you addAssociated methodsReactionMenu.add_row(data: str)ReactionMenu.clear_all_row_data()ReactionMenu.set_main_pages(*embeds: Embed)ReactionMenu.set_last_pages(*embeds: Embed)The kwargs specifically made for a dynamic menu are:rows_requested- The amount of rows you would like on each embed page before making a new pageReactionMenu(..., rows_requested=5)custom_embed- An embed you have created to use as the embed pages. Used for your menu aestheticReactionMenu(..., custom_embed=red_embed)wrap_in_codeblock- The language identifier when wrapping your data in a discord codeblock.ReactionMenu(..., wrap_in_codeblock='py')Adding Rows/datamenu=ReactionMenu(ctx,menu_type=ReactionMenu.TypeEmbedDynamic,rows_requested=5)fordataindatabase.request('SELECT * FROM customers'):menu.add_row(data)Deleting DataYou can remove all the data you've added to a menu by usingmenu.clear_all_row_data()Main/Last PagesWhen using a dynamic menu, the only embed pages you see are from the data you've added. But if you would like to show more pages other than just the data, you can use methodsReactionMenu.set_main_pages()andReactionMenu.set_last_pages(). Setting the main page(s), the embeds you set will be the first embeds that are shown when the menu starts. Setting the last page(s) are the last embeds shownmenu.set_main_pages(welcome_embed,announcement_embed)fordatainget_information():menu.add_row(data)menu.set_last_pages(additional_info_embed)# NOTE: setting main/last pages can be set in any orderReactionButtons and ButtonTypesButtons/button types are used when you want to add a reaction to the menu that does a certain function. Buttons and button types work together to achieve the desired action.class reactionmenu.ReactionButton(*, emoji: str, linked_to: ButtonType, **kwargs)Parameters of the ReactionButton constructoremoji(str) The emoji you would like to use as the reactionlinked_to(ReactionButton.Type) When the reaction is pressed, this is what determines what it will doKwargs of the ReactionButton constructorNameTypeDefault ValueUsed forembeddiscord.EmbedNoneWhen the reaction is pressed, go to the specified embednamestrNoneThe name of the buttondetailsinfo belowNoneAssigns the function and it's arguments to call when aReactionButtonwithReactionButton.Type.CALLERis pressedeventReactionButton.EventNoneDetermine when a button should be removed depending on how many times it has been pressedskipReactionButton.SkipNoneSet the action and the amount of pages to skip when using alinked_toofReactionButton.Type.SKIP. For example using this button type, setting the action to "+" and the amount 3. If you are on "Page 1/20", pressing that button will bring you to "Page 4/20"Attributes for ReactionButtonPropertyReturn TypeInfoclicked_bySet[discord.Member]The members who clicked the buttontotal_clicksintAmount of clicks from the buttonlast_clickedOptional[datetime.datetime]The time in UTC for when the button was last clickedmenuOptional[ReactionMenu]The menu the button is attached toAssociated methodsReactionMenu.add_button(button: ReactionButton)ReactionMenu.remove_all_buttons()ReactionMenu.remove_button(button: ReactionButton)ReactionMenu.get_button(identity: Union[str, int], *, search_by='name')ReactionButton.set_caller_details(func: Callable[..., None], *args, **kwargs)All ButtonTypesTypeInfoReactionButton.Type.NEXT_PAGEGo to the next page in the menu sessionReactionButton.Type.PREVIOUS_PAGEGo to the previous page in the menu sessionReactionButton.Type.GO_TO_FIRST_PAGEGo to the first page in the menu sessionReactionButton.Type.GO_TO_LAST_PAGEGo to the last page in the menu sessionReactionButton.Type.GO_TO_PAGEPrompts you to type in the page you'd like to go toReactionButton.Type.END_SESSIONStops the session and deletes the menu messageReactionButton.Type.CUSTOM_EMBEDUsed separately from the navigation buttons. Once pressed, go to the specified embedReactionButton.Type.CALLERUsed when specifying the function to call and it's arguments when the button is pressedReactionButton.Type.SKIPUsed to paginate through multiple pages in a single button pressAdding ButtonsYou can add buttons (reactions) to the menu using aReactionButton. Below are examples on how to use eachButtonType.NOTE:ReactionButtons withReactionButton.Type.CALLERare a little different, so there is a dedicated section explaining how they work and how to implement them further belowmenu=ReactionMenu(...)# first and last pagesfpb=ReactionButton(emoji='⏪',linked_to=ReactionButton.Type.GO_TO_FIRST_PAGE)lpb=ReactionButton(emoji='⏩',linked_to=ReactionButton.Type.GO_TO_LAST_PAGE)# go to pagegtpb=ReactionButton(emoji='🔢',linked_to=ReactionButton.Type.GO_TO_PAGE)# end sessionesb=ReactionButton(emoji='⏹️',linked_to=ReactionButton.Type.END_SESSION)# custom embedceb=ReactionButton(emoji='😎',linked_to=ReactionButton.Type.CUSTOM_EMBED,embed=discord.Embed(title='Hello'))# skip buttonsb=ReactionButton(emoji='5️⃣',linked_to=ReactionButton.Type.SKIP,skip=ReactionButton.Skip(action='+',amount=5))menu.add_button(fpb)...Deleting ButtonsRemove all buttons withmenu.remove_all_buttons(). You can also remove an individual button using its name if you have it set, or the button object itself withmenu.remove_button()ReactionButtons with ReactionButton.Type.CALLERReactionButton.Type.CALLERbuttons are used to implement your own functionality into the menu. Maybe you want to add a button that creates a text channel, sends a message, or add something to a database, whatever it may be. In order to work withReactionButton.Type.CALLER, use the class method below.ReactionButton.set_caller_details(func: Callable[..., None], *args, **kwargs)This class method is used to setup a function and it's arguments that are later called when the button is pressed. TheReactionButtonconstructor has the kwargdetails, and that's what you'll use with.set_caller_details()to assign the values needed. Some examples are below on how to properly implementReactionButton.Type.CALLER@bot.command()asyncdefuser(ctx,name,*,message):awaitctx.send(f"Hi{name}!{message}. We're glad you're here!")defcar(year,make,model):print(f"I have a{year}{make}{model}")ub=ReactionButton(emoji='👋',linked_to=ReactionButton.Type.CALLER,details=ReactionButton.set_caller_details(user,ctx,'Defxult',message='Welcome to the server'))cb=ReactionButton(emoji='🚗',linked_to=ReactionButton.Type.CALLER,details=ReactionButton.set_caller_details(car,2021,'Ford','Mustang'))NOTE:The function you pass in should not return anything. Calling functions withReactionButton.Type.CALLERdoes not store or handle anything returned by that functionReactionButton MethodsTheReactionButtonclass comes with a set factory methods (class methods) that returns aReactionButtonwith parameters set according to theirlinked_to.ReactionButton.back()emoji: "◀️"linked_to:ReactionButton.Type.PREVIOUS_PAGEReactionButton.next()emoji: "▶️"linked_to:ReactionButton.Type.NEXT_PAGEReactionButton.go_to_first_page()emoji: "⏪"linked_to:ReactionButton.Type.GO_TO_FIRST_PAGEReactionButton.go_to_last_page()emoji: "⏩"linked_to:ReactionButton.Type.GO_TO_LAST_PAGEReactionButton.go_to_page()emoji: "🔢"linked_to:ReactionButton.Type.GO_TO_PAGEReactionButton.end_session()emoji: "⏹️"linked_to:ReactionButton.Type.END_SESSIONReactionButton.all()Returns alistofReactionButtonin the following order.go_to_first_page().back().next().go_to_last_page().go_to_page().end_session()ReactionButton.generate_skip(emoji: str, action: str, amount: int)emoji:<emoji>linked_to:ReactionButton.Type.SKIPskip:ReactionButton.Skip(<action>, <amount>)Setting LimitsIf you'd like, you can limit the amount of reaction menus that can be active at the same timeper"guild", "member", or "channel"Associated CLASS MethodsReactionMenu.set_sessions_limit(limit: int, per='guild', message='Too many active menus. Wait for other menus to be finished.')ReactionMenu.remove_limit()Example:@bot.command()asyncdeflimit(ctx):ReactionMenu.set_sessions_limit(3,per='member',message='Sessions are limited to 3 per member')With the above example, only 3 menus can be active at once for each member, and if they try to create more before their other menu's are finished, they will get an error message saying "Sessions are limited to 3 per member".ReactionButton EventsYou can set aReactionButtonto be removed when it has been pressed a certain amount of timesclass ReactionButton.Event(event_type: str, value: int)Parameters for ReactionButton.Eventevent_type(str) The action to take. The only available option is "remove"value(int) The amount set for the specified event. Must be >= 1. If value is <= 0, it is implicitly set to 1Example:menu=ReactionMenu(ctx,...)# remove a button after 10 clicksbutton=ReactionButton(...,event=ReactionButton.Event('remove',10))menu.add_button(button)NOTE:Not ideal for buttons with alinked_toofReactionButton.Type.END_SESSIONReactionMenu RelaysMenu relays are functions that are called anytime a button that is apart of a menu is pressed. It is considered as an extension of aReactionButtonwith alinked_toofReactionButton.Type.CALLER. Unlike caller buttons which provides no details about the interactions on the menu, relays do.Associated methodsReactionMenu.set_relay(func: Callable[[NamedTuple], None], *, only: Optional[List[ReactionButton]]=None)ReactionMenu.remove_relay()When creating a function for your relay, that function must contain a single positional argument. When a button is pressed, aRelayPayloadobject (a named tuple) is passed to that function. The attributes ofRelayPayloadare:member(discord.Member) The person who pressed the buttonbutton(ReactionButton) The button that was pressedExample:asyncdefenter_giveaway(payload):member=payload.memberchannel=payload.button.menu.message.channelawaitchannel.send(f"{member.mention}, you've entered the giveaway!")menu=ReactionMenu(ctx,...)menu.set_relay(enter_giveaway)Theset_relaymethod comes with theonlyparameter. If that parameter isNone, all buttons that are pressed will be relayed. You can provide alistof buttons to that parameter so only button presses from those specified buttons will be relayed.defexample(payload):...menu=ReactionMenu(ctx,...)back_button=ReactionButton.back()next_button=ReactionButton.next()menu.set_relay(example,only=[back_button])Starting/Stopping the ReactionMenuAssociated methodsawait ReactionMenu.start(*, send_to=None, reply=False)await ReactionMenu.stop(*, delete_menu_message=False, clear_reactions=False)When starting the menu, you have the option to send the menu to a certain channel. Parametersend_tois the channel you'd like to send the menu to. You can setsend_toas the channel name (str), channel ID (int), or channel object (discord.TextChannel/discord.Thread). Example:menu=ReactionMenu(...)# channel nameawaitmenu.start(send_to='bot-commands')# channel IDawaitmenu.start(send_to=1234567890123456)# channel objectchannel=guild.get_channel(1234567890123456)awaitmenu.start(send_to=channel)# there's no need to specify send_to unless you want the menu to be sent to a different channel# from the one you're sending the initial message/using the command in. the menu can be started# in the current channel by omitting the send_to parameterawaitmenu.start()NOTE:send_tois not valid if a menu was started in DM'sFull ExampleHere is a basic implementation ofReactionMenuthat you can copy & paste for a quick demonstration.importasyncioimportdiscordfromdiscord.extimportcommandsfromreactionmenuimportReactionMenu,ReactionButtonbot=commands.Bot(command_prefix='!',intents=discord.Intents.all())asyncdefstart_bot():asyncwithbot:awaitbot.start('...')@bot.command()asyncdefexample(ctx):menu=ReactionMenu(ctx,menu_type=ReactionMenu.TypeEmbed)formemberinctx.guild.members:ifmember.avatar:embed=discord.Embed(description=f'Joined{member.joined_at.strftime("%b.%d, %Y")}')embed.set_author(name=member.name,icon_url=member.avatar.url)menu.add_page(embed)menu.add_button(ReactionButton.back())menu.add_button(ReactionButton.next())menu.add_button(ReactionButton.end_session())awaitmenu.start()asyncio.run(start_bot())ViewMenuclass reactionmenu.ViewMenu(method: Union[Context, discord.Interaction], /, *, menu_type: MenuType, **kwargs)AViewMenuis a menu that uses discords Buttons feature. With buttons, you can enable and disable them, set a certain color for them with emojis, have buttons that send hidden messages, and add hyperlinks. This library offers a broader range of functionalities such as who pressed the button, how many times it was pressed and more. It uses views (discord.ui.View) to implement the Buttons functionality, but uses some of its own methods in order to make a Button pagination menu simple.Click to show ViewMenu documentationHow to importfromreactionmenuimportViewMenu,ViewButtonParameters of the ViewMenu constructormethod(Union[discord.ext.commands.Context, discord.Interaction]) A context or interaction objectmenu_type(MenuType) The configuration of the menuViewMenu.TypeEmbed, a normal embed pagination menuViewMenu.TypeEmbedDynamic, an embed pagination menu with dynamic dataViewMenu.TypeText, a text only pagination menuKwargs of the ViewMenu constructorNameTypeDefault ValueUsed forInfowrap_in_codeblockstrNoneViewMenu.TypeEmbedDynamicThe discord codeblock language identifier to wrap your data in. Example:ViewMenu(ctx, ..., wrap_in_codeblock='py')custom_embeddiscord.EmbedNoneViewMenu.TypeEmbedDynamicEmbed object to use when adding data withViewMenu.add_row(). Used for styling purposesdelete_on_timeoutboolFalseAll menu typesDelete the menu when it times outdisable_items_on_timeoutboolTrueAll menu typesDisable the items on the menu when the menu times outremove_items_on_timeoutboolFalseAll menu typesRemove the items on the menu when the menu times outonly_rolesList[discord.Role]NoneAll menu typesIf set, only members with any of the given roles are allowed to control the menu. The menu owner can always control the menutimeoutUnion[int, float, None]60.0All menu typesThe timer for when the menu times out. Can beNonefor no timeoutshow_page_directorboolTrueAll menu typesShown at the bottom of each embed page. "Page 1/20"namestrNoneAll menu typesA name you can set for the menustylestr"Page $/&"All menu typesA custom page director style you can select. "$" represents the current page, "&" represents the total amount of pages. Example:ViewMenu(ctx, ..., style='On $ out of &')all_can_clickboolFalseAll menu typesSets if everyone is allowed to control when pages are 'turned' when buttons are clickeddelete_interactionsboolTrueAll menu typesDelete the prompt message by the bot and response message by the user when asked what page they would like to go to when usingViewButton.ID_GO_TO_PAGErows_requestedintNoneViewMenu.TypeEmbedDynamicThe amount of information perViewMenu.add_row()you would like applied to each embed pagePages for ViewMenuDepending on themenu_type, pages can either be astr,discord.Embed, or a combination ofcontentorfiles(example below)If themenu_typeisViewMenu.TypeEmbed, use embedsIf themenu_typeisViewMenu.TypeText(text only menu) orViewMenu.TypeEmbedDynamic(embed only menu), use strings.Associated methodsViewMenu.add_page(embed: discord.Embed=MISSING, content: Optional[str]=None, files: Optional[Sequence[discord.File]]=None)ViewMenu.add_pages(pages: Sequence[Union[discord.Embed, str]])ViewMenu.add_row(data: str)ViewMenu.remove_all_pages()ViewMenu.clear_all_row_data()ViewMenu.remove_page(page_number: int)ViewMenu.set_main_pages(*embeds: Embed)ViewMenu.set_last_pages(*embeds: Embed)Adding Pages# ViewMenu.TypeEmbedmenu=ViewMenu(method,menu_type=ViewMenu.TypeEmbed)menu.add_page(summer_embed)menu.add_page(winter_embed)# ViewMenu.TypeTextmenu=ViewMenu(method,menu_type=ViewMenu.TypeText)menu.add_page(content='Its so hot!')menu.add_page(content='Its so cold!')ViewMenu.TypeTextATypeTextmenu is a text based pagination menu. No embeds are involved in the pagination process, only plain text is used.Stacked PagesWithv3.1.0+, you can paginate with more than just an embed or text. You can combine text, embeds, as well as files. But depending on themenu_typethe combination can be restricted. Here is an example of a menu with amenu_typeofTypeEmbedthat is stacked.# You can use regular commands as well@bot.tree.command(description="These are stacked pages",guild=discord.Object(id=...))asyncdefstacked(interaction:discord.Interaction):menu=ViewMenu(interaction,menu_type=ViewMenu.TypeEmbed)menu.add_page(discord.Embed(title="My Embed"),content="This content is stacked on top of a file",files=[discord.File("stacked.py")])menu.add_page(discord.Embed(title="Hey Wumpos, can you say hi to the person reading this? 😃"))menu.add_page(discord.Embed(title="Hi, I'm Wumpos!"),files=[discord.File("wumpos.gif")])menu.add_button(ViewButton.back())menu.add_button(ViewButton.next())awaitmenu.start()Since themenu_typeisTypeEmbed, there always has to be an embed on each page. If themenu_typewasTypeText, embeds aren't allowed and you will be restricted to only using thefilesparameter.ViewMenu.TypeEmbedDynamicA dynamic menu is used when you do not know how much information will be applied to the menu. For example, if you were to request information from a database, that information can always change. You query something and you might get 1,500 results back, and the next maybe only 800. A dynamic menu pieces all this information together for you and adds it to an embed page by rows of data.ViewMenu.add_row()is best used in some sort ofIterablewhere everything can be looped through, but only add the amount of data you want to the menu page.NOTE:In a dynamic menu, all added data is placed in the description section of an embed. If you choose to use acustom_embed, all text in the description will be overridden with the data you addAssociated methodsViewMenu.add_row(data: str)ViewMenu.clear_all_row_data()ViewMenu.set_main_pages(*embeds: Embed)ViewMenu.set_last_pages(*embeds: Embed)The kwargs specifically made for a dynamic menu are:rows_requested- The amount of rows you would like on each embed page before making a new pageViewMenu(..., rows_requested=5)custom_embed- An embed you have created to use as the embed pages. Used for your menu aestheticViewMenu(..., custom_embed=red_embed)wrap_in_codeblock- The language identifier when wrapping your data in a discord codeblock.ViewMenu(..., wrap_in_codeblock='py')Adding Rows/datamenu=ViewMenu(ctx,menu_type=ViewMenu.TypeEmbedDynamic,rows_requested=5)fordataindatabase.request('SELECT * FROM customers'):menu.add_row(data)Deleting DataYou can remove all the data you've added to a menu by usingmenu.clear_all_row_data()Main/Last PagesWhen using a dynamic menu, the only embed pages you see are from the data you've added. But if you would like to show more pages other than just the data, you can use methodsViewMenu.set_main_pages()andViewMenu.set_last_pages(). Setting the main page(s), the embeds you set will be the first embeds that are shown when the menu starts. Setting the last page(s) are the last embeds shownmenu.set_main_pages(welcome_embed,announcement_embed)fordatainget_information():menu.add_row(data)menu.set_last_pages(additional_info_embed)# NOTE: setting main/last pages can be set in any orderButtons for ViewMenuButtons are what you use to interact with the menu. Unlike reactions, they look cleaner, provides less rate limit issues, and offer more in terms of interactions. Enable and disable buttons, use markdown hyperlinks in it's messages, and even send hidden messages.Associated methodsViewMenu.add_button(button: ViewButton)ViewMenu.disable_all_buttons()ViewMenu.disable_button(button: ViewButton)ViewMenu.enable_all_buttons()ViewMenu.enable_button(button: ViewButton)ViewMenu.get_button(identity: str, *, search_by='label')ViewMenu.remove_all_buttons()ViewMenu.remove_button(button: ViewButton)await ViewMenu.refresh_menu_items()ViewButtonclass reactionmenu.ViewButton(*, style=discord.ButtonStyle.secondary, label=None, disabled=False, custom_id=None, url=None, emoji=None, followup=None, event=None, **kwargs)AViewButtonis a class that represents the discord button. It is a subclass ofdiscord.ui.Button.The following are the rules set by Discord for Buttons:Link buttons don't send interactions to the Discord App, so link button statistics (it's properties) are not trackedNon-link buttonsmusthave acustom_id, and cannot have aurlLink buttonsmusthave aurl, and cannot have acustom_idThere cannot be more than 25 buttons per messageParameters of the ViewButton constructorstyle(discord.ButtonStyle) The button stylelabel(str) The text on the buttoncustom_id(str) An ID to determine what action that button should take. Available IDs:ViewButton.ID_NEXT_PAGEViewButton.ID_PREVIOUS_PAGEViewButton.ID_GO_TO_FIRST_PAGEViewButton.ID_GO_TO_LAST_PAGEViewButton.ID_GO_TO_PAGEViewButton.ID_END_SESSIONViewButton.ID_CALLERViewButton.ID_SEND_MESSAGEViewButton.ID_CUSTOM_EMBEDViewButton.ID_SKIPemoji(Union[str, discord.PartialEmoji]) Emoji used for the buttonViewButton(..., emoji='😄')ViewButton(..., emoji='<:miscTwitter:705423192818450453>')ViewButton(..., emoji='\U000027a1')ViewButton(..., emoji='\N{winking face}')url(str) URL for a button with stylediscord.ButtonStyle.linkdisabled(bool) If the button should be disabledfollowup(ViewButton.Followup) The message sent after the button is pressed. Only available for buttons that have acustom_idofViewButton.ID_CALLERorViewButton.ID_SEND_MESSAGE.ViewButton.Followupis a class that has parameters similar todiscord.abc.Messageable.send(), and is used to control if a message is ephemeral, contains a file, embed, tts, etc...event(ViewButton.Event) Set a button to be disabled or removed when it has been pressed a certain amount of timesKwargs of the ViewButton constructorNameTypeDefault ValueUsed fornamestrNoneThe name of the buttonskipViewButton.SkipNoneSet the action and the amount of pages to skip when using acustom_idofViewButton.ID_SKIP. For example, setting the action to "+" and the amount 3. If you are on "Page 1/20", pressing that button will bring you to "Page 4/20"persistboolFalsePrevents link buttons from being disabled/removed when the menu times out or is stopped so they can remain clickableAttributes for ViewButtonPropertyReturn TypeInfoclicked_bySet[discord.Member]The members who clicked the buttontotal_clicksintAmount of clicks from the buttonlast_clickedOptional[datetime.datetime]The time in UTC for when the button was last clickedmenuOptional[ViewMenu]The menu the button is attached toAdding a ViewButtonfromreactionmenuimportViewMenu,ViewButtonmenu=ViewMenu(ctx,menu_type=ViewMenu.TypeEmbed)# Link buttonlink_button=ViewButton(style=discord.ButtonStyle.link,emoji='🌍',label='Link to Google',url='https://google.com')menu.add_button(link_button)# Skip buttonskip=ViewButton(style=discord.ButtonStyle.primary,label='+5',custom_id=ViewButton.ID_SKIP,skip=ViewButton.Skip(action='+',amount=5))menu.add_button(skip)# ViewButton.ID_PREVIOUS_PAGEback_button=ViewButton(style=discord.ButtonStyle.primary,label='Back',custom_id=ViewButton.ID_PREVIOUS_PAGE)menu.add_button(back_button)# ViewButton.ID_NEXT_PAGEnext_button=ViewButton(style=discord.ButtonStyle.secondary,label='Next',custom_id=ViewButton.ID_NEXT_PAGE)menu.add_button(next_button)# All other ViewButton are created the same way as the last 2 EXCEPT# 1 - ViewButton.ID_CALLER# 2 - ViewButton.ID_SEND_MESSAGE# 3 - ViewButton.ID_CUSTOM_EMBED# ViewButton.ID_CALLERdefsay_hello(name:str):print('Hello',name)call_followup=ViewButton.Followup(details=ViewButton.Followup.set_caller_details(say_hello,'John'))menu.add_button(ViewButton(label='Say hi',custom_id=ViewButton.ID_CALLER,followup=call_followup))# ViewButton.ID_SEND_MESSAGEmsg_followup=ViewButton.Followup('This message is hidden!',ephemeral=True)menu.add_button(ViewButton(style=discord.ButtonStyle.green,label='Message',custom_id=ViewButton.ID_SEND_MESSAGE,followup=msg_followup))# ViewButton.ID_CUSTOM_EMBEDcustom_embed_button=ViewButton(style=discord.ButtonStyle.blurple,label='Social Media Info',custom_id=ViewButton.ID_CUSTOM_EMBED,followup=ViewButton.Followup(embed=discord.Embed(...)))NOTE:When it comes to buttons with acustom_idofViewButton.ID_CALLER,ViewButton.ID_SEND_MESSAGE,ViewButton.ID_CUSTOM_EMBED, or link buttons, you can add as many as you'd like as long as in total it's 25 buttons or less. For all other button ID's, each menu can only have one.Using SelectsSelects are used when you'd like to categorize information in your menu. Selects can only be used when the menu'smenu_typeisTypeEmbed. You should keep in mind that discords limitations on how many menu UI items (rows) can be applied to each message.Associated MethodsViewMenu.add_select(select: ViewSelect)ViewMenu.remove_select(select: ViewSelect)ViewMenu.remove_all_selects()ViewMenu.disable_select(select: ViewSelect)ViewMenu.disable_all_selects()ViewMenu.enable_select(select: ViewSelect)ViewMenu.enable_all_selects()ViewMenu.get_select(title: Union[str, None])Example:fromreactionmenuimportViewMenu,ViewSelect,Pagemenu=ViewMenu(ctx,menu_type=ViewMenu.TypeEmbed)menu.add_page(discord.Embed(title="A showcase of console video games",color=discord.Color.blurple()))menu.add_select(ViewSelect(title="Console Video Games",options={discord.SelectOption(label="PlayStation",emoji="<:PlayStation:549638412538478602>"):[Page(embed=discord.Embed(title="Ratchet & Clank",description=...,color=discord.Color.yellow()).set_image(url=...)),Page(embed=discord.Embed(title="God of War",description=...,color=discord.Color.blue()).set_image(url=...))],discord.SelectOption(label="Xbox",emoji="<:Xbox:501880493285834752>"):[Page(embed=discord.Embed(title="Halo Infinite",description=...,color=discord.Color.green()).set_image(url=...)),Page(embed=discord.Embed(title="Gears of War 4",description=...,color=discord.Color.red()).set_image(url=...))]}))menu.add_button(ViewButton.back())menu.add_button(ViewButton.next())awaitmenu.start()Go to page navigationYou can use this type of select when you'd like to use the UI to select a page to go to.Associated methodsViewMenu.add_go_to_select(goto: ViewSelect.GoTo)ViewMenu.enable_go_to_select(goto: ViewSelect.GoTo)ViewMenu.enable_all_go_to_selects()ViewMenu.disable_go_to_select(goto: ViewSelect.GoTo)ViewMenu.disable_all_go_to_selects()ViewMenu.remove_go_to_select(goto: ViewSelect.GoTo)ViewMenu.remove_all_go_to_selects()Thepage_numbersparameter forViewSelect.GoTocan be used with 3 different typesList[int]If set to a list of integers, those specified values are the only options that are available when the select is clickedpage_numbers=[1, 5, 10]Dict[int, Union[str, discord.Emoji, discord.PartialEmoji]]You can use this type if you'd like to utilize emojis in your selectpage_numbers={1 : "🗯️", 2 : "📺"}ellipsisYou can set aliteralellipsis to have the library automatically assign all page numbers to the amount of pages that you've added to the menu. This can come in handy if you have 25 pages or lesspage_numbers=...NOTE: Setting thepage_numbersparameter to an ellipsis (...) only works as intended if you've added the go to select AFTER you've added pages to the menu@bot.command()asyncdefnavigate(ctx):menu=ViewMenu(ctx,menu_type=ViewMenu.TypeEmbed)menu.add_page(discord.Embed(title="Twitter").set_image(url="..."))menu.add_page(discord.Embed(title="YouTube").set_image(url="..."))menu.add_page(discord.Embed(title="Discord").set_image(url="..."))# ...menu.add_go_to_select(ViewSelect.GoTo(title="Go to page...",page_numbers=...))menu.add_button(ViewButton.back())menu.add_button(ViewButton.next())awaitmenu.start()Updating ViewButton and PagesAssociated methodsawait ViewMenu.refresh_menu_items()await ViewMenu.update(*, new_pages: Union[List[Union[Embed, str]], None], new_buttons: Union[List[ViewButton], None])When the menu is running, you can update the pages or buttons on the menu. UsingViewMenu.update(), you can replace the pages and buttons. UsingViewMenu.refresh_menu_items()updates the buttons you have changed.Updating a Button@bot.command()asyncdefmenu(ctx):menu=ViewMenu(...,name='test')link_button=ViewButton(...,label='Link')menu.add_button(link_button)menu.add_page(...)awaitmenu.start()@bot.command()asyncdefdisable(ctx):menu=ViewMenu.get_session('test')link_button=menu[0].get_button('Link',search_by='label')menu.disable_button(link_button)awaitmenu.refresh_menu_items()If the buttons are not refreshed withViewMenu.refresh_menu_items(), the menu will not be updated when changing a button.Updating Pages and ButtonsMethodViewMenu.update(...)is used when you want to replace all or a few of the buttons on the menu.menu=ViewMenu(...)# in a different .command()awaitmenu.update(new_pages=[hello_embed,goodbye_embed],new_buttons=[link_button,next_button])NOTE: When usingViewMenu.update(...), there is no need to useViewMenu.refresh_menu_items()because they are updated during the update call.ViewButton MethodsTheViewButtonclass comes with a set factory methods (class methods) that returns aViewButtonwith parameters set according to theircustom_id(excluding link buttons).ViewButton.link(label: str, url: str)style:discord.ButtonStyle.linklabel:<label>url:<url>ViewButton.back()style:discord.ButtonStyle.graylabel: "Back"custom_id:ViewButton.ID_PREVIOUS_PAGEViewButton.next()style:discord.ButtonStyle.graylabel: "Next"custom_id:ViewButton.ID_NEXT_PAGEViewButton.go_to_first_page()style:discord.ButtonStyle.graylabel: "First Page"custom_id:ViewButton.ID_GO_TO_FIRST_PAGEViewButton.go_to_last_page()style:discord.ButtonStyle.graylabel: "Last Page"custom_id:ViewButton.ID_GO_TO_LAST_PAGEViewButton.go_to_page()style:discord.ButtonStyle.graylabel: "Page Selection"custom_id:ViewButton.ID_GO_TO_PAGEViewButton.end_session()style:discord.ButtonStyle.graylabel: "Close"custom_id:ViewButton.ID_END_SESSIONViewButton.all()Returns alistofViewButtonin the following order.go_to_first_page().back().next().go_to_last_page().go_to_page().end_session()ViewButton.all_with_emojis()Returns alistofViewButtonwith theiremojiparameters already set in the following order.go_to_first_page().back().next().go_to_last_page().go_to_page().end_session()ViewButton.generate_skip(label: str, action: str, amount: int)style:discord.ButtonStyle.graylabel:<label>custom_id:ViewButton.ID_SKIPskip:ViewButton.Skip(<action>, <amount>)menu=ViewMenu(ctx,...)menu.add_page(...)menu.add_page(...)menu.add_button(ViewButton.back())menu.add_button(ViewButton.next())awaitmenu.start()ViewButton EventsYou can set aViewButtonto be disabled or removed when it has been pressed a certain amount of timesclass ViewButton.Event(event_type: str, value: int)Parameters for ViewButton.Eventevent_type(str) The action to take. Can either be "disable" or "remove"value(int) The amount set for the specified event. Must be >= 1. If value is <= 0, it is implicitly set to 1Example:menu=ViewMenu(ctx,...)# disable a button after 5 clicksbutton_1=ViewButton(...,event=ViewButton.Event('disable',5))menu.add_button(button_1)# remove a button after 10 clicksbutton_2=ViewButton(...,event=ViewButton.Event('remove',10))menu.add_button(button_2)NOTE:Not valid for link buttons. Also not ideal for buttons with acustom_idofViewButton.ID_END_SESSIONViewMenu RelaysMenu relays are functions that are called anytime a button that is apart of a menu is pressed. It is considered as an extension of aViewButtonwith an ID ofViewButton.ID_CALLER. Unlike caller buttons which provides no details about the interactions on the menu, relays do.Associated methodsViewMenu.set_relay(func: Callable[[NamedTuple], None], *, only: Optional[List[ViewButton]]=None)ViewMenu.remove_relay()When creating a function for your relay, that function must contain a single positional argument. When a button is pressed, aRelayPayloadobject (a named tuple) is passed to that function. The attributes ofRelayPayloadare:member(discord.Member) The person who pressed the buttonbutton(ViewButton) The button that was pressedExample:asyncdefenter_giveaway(payload):member=payload.memberchannel=payload.button.menu.message.channelawaitchannel.send(f"{member.mention}, you've entered the giveaway!")menu=ViewMenu(ctx,...)menu.set_relay(enter_giveaway)Theset_relaymethod comes with theonlyparameter. If that parameter isNone, all buttons that are pressed will be relayed (except link buttons because they don't send interaction events). You can provide alistof buttons to that parameter so only button presses from those specified buttons will be relayed.defexample(payload):...menu=ViewMenu(ctx,...)back_button=ViewButton.back()next_button=ViewButton.next()menu.set_relay(example,only=[back_button])Starting/Stopping the ViewMenuAssociated methodsawait ViewMenu.start(*, send_to=None, reply=False)await ViewMenu.stop(*, delete_menu_message=False, remove_buttons=False, disable_buttons=False)When starting the menu, you have the option to send the menu to a certain channel. Parametersend_tois the channel you'd like to send the menu to. You can setsend_toas the channel name (str), channel ID (int), or channel object (discord.TextChannel/discord.Thread). Example:menu=ViewMenu(...)# channel nameawaitmenu.start(send_to='bot-commands')# channel IDawaitmenu.start(send_to=1234567890123456)# channel objectchannel=guild.get_channel(1234567890123456)awaitmenu.start(send_to=channel)# there's no need to specify send_to unless you want the menu to be sent to a different channel# from the one you're sending the initial message/using the command in. the menu can be started# in the current channel by omitting the send_to parameterawaitmenu.start()NOTE:send_tois not valid if a menu was started in DM'sOnly one option is available when stopping the menu. If you have multiple parameters asTrue, only one will executedelete_menu_message>disable_buttonsdisable_buttons>remove_buttonsFull ExampleHere is a basic implementation ofViewMenuthat you can copy & paste for a quick demonstration.importasyncioimportdiscordfromdiscord.extimportcommandsfromreactionmenuimportViewMenu,ViewButtonbot=commands.Bot(command_prefix='!',intents=discord.Intents.all())asyncdefstart_bot():asyncwithbot:awaitbot.start('...')@bot.command()asyncdefexample(ctx):menu=ViewMenu(ctx,menu_type=ViewMenu.TypeEmbed)formemberinctx.guild.members:ifmember.avatar:embed=discord.Embed(description=f'Joined{member.joined_at.strftime("%b.%d, %Y")}')embed.set_author(name=member.name,icon_url=member.avatar.url)menu.add_page(embed)menu.add_button(ViewButton.back())menu.add_button(ViewButton.next())menu.add_button(ViewButton.end_session())awaitmenu.start()asyncio.run(start_bot())