Package: disnake-ext-menus
disnake-ext-menusAn experimental extension menu that makes working with reaction menus a bit easier.Installingpython-mpipinstall-Udisnake-ext-menusGetting StartedTo whet your appetite, the following examples show the fundamentals on how to create menus.The first example shows a basic menu that has a stop button and two reply buttons:fromdisnake.extimportmenusclassMyMenu(menus.Menu):asyncdefsend_initial_message(self,ctx,channel):returnawaitchannel.send(f'Hello{ctx.author}')@menus.button('\N{THUMBS UP SIGN}')asyncdefon_thumbs_up(self,payload):awaitself.message.edit(content=f'Thanks{self.ctx.author}!')@menus.button('\N{THUMBS DOWN SIGN}')asyncdefon_thumbs_down(self,payload):awaitself.message.edit(content=f"That's not nice{self.ctx.author}...")@menus.button('\N{BLACK SQUARE FOR STOP}\ufe0f')asyncdefon_stop(self,payload):self.stop()Now, within a command we just instantiate it and we start it like so:@bot.command()asyncdefmenu_example(ctx):m=MyMenu()awaitm.start(ctx)If an error happens then an exception of typemenus.MenuErroris raised.This second example shows a confirmation menu and how we can compose it and use it later:fromdisnake.extimportmenusclassConfirm(menus.Menu):def__init__(self,msg):super().__init__(timeout=30.0,delete_message_after=True)self.msg=msgself.result=Noneasyncdefsend_initial_message(self,ctx,channel):returnawaitchannel.send(self.msg)@menus.button('\N{WHITE HEAVY CHECK MARK}')asyncdefdo_confirm(self,payload):self.result=Trueself.stop()@menus.button('\N{CROSS MARK}')asyncdefdo_deny(self,payload):self.result=Falseself.stop()asyncdefprompt(self,ctx):awaitself.start(ctx,wait=True)returnself.resultThen when it comes time to use it we can do it like so:@bot.command()asyncdefdelete_things(ctx):confirm=awaitConfirm('Delete everything?').prompt(ctx)ifconfirm:awaitctx.send('deleted...')PaginationThe meat of the library is theMenuclass but aMenuPagesclass is provided for the common use case of actually making a pagination session.TheMenuPagesworks similar toMenuexcept things are separated into aPageSource. The actualMenuPagesrarely needs to be modified, instead we pass in aPageSourcethat deals with the data representation and formatting of the data we want to paginate.The library comes with a few built-in page sources:ListPageSource: The basic source that deals with a list of items.GroupByPageSource: A page source that groups a list into multiple sublists similar toitertools.groupby.AsyncIteratorPageSource: A page source that works with async iterators for lazy fetching of data.None of these page sources deal with formatting of data, leaving that up to you.For the sake of example, here's a basic list source that is paginated:fromdisnake.extimportmenusclassMySource(menus.ListPageSource):def__init__(self,data):super().__init__(data,per_page=4)asyncdefformat_page(self,menu,entries):offset=menu.current_page*self.per_pagereturn'\n'.join(f'{i}.{v}'fori,vinenumerate(entries,start=offset))# somewhere else:pages=menus.MenuPages(source=MySource(range(1,100)),clear_reactions_after=True)awaitpages.start(ctx)Theformat_pagecan return either astrfor content,disnake.Embedfor an embed, or adictto pass into the kwargs ofMessage.edit.Some more examples usingGroupByPageSource:fromdisnake.extimportmenusclassTest:def__init__(self,key,value):self.key=keyself.value=valuedata=[Test(key=key,value=value)forkeyin['test','other','okay']forvalueinrange(20)]classSource(menus.GroupByPageSource):asyncdefformat_page(self,menu,entry):joined='\n'.join(f'{i}. <Test value={v.value}>'fori,vinenumerate(entry.items,start=1))returnf'**{entry.key}**\n{joined}\nPage{menu.current_page+1}/{self.get_max_pages()}'pages=menus.MenuPages(source=Source(data,key=lambdat:t.key,per_page=12),clear_reactions_after=True)awaitpages.start(ctx)Another one showingAsyncIteratorPageSource:fromdisnake.extimportmenusclassTest:def__init__(self,value):self.value=valuedef__repr__(self):returnf'<Test value={self.value}>'asyncdefgenerate(number):foriinrange(number):yieldTest(i)classSource(menus.AsyncIteratorPageSource):def__init__(self):super().__init__(generate(9),per_page=4)asyncdefformat_page(self,menu,entries):start=menu.current_page*self.per_pagereturnf'\n'.join(f'{i}.{v!r}'fori,vinenumerate(entries,start=start))pages=menus.MenuPages(source=Source(),clear_reactions_after=True)awaitpages.start(ctx)