An extension module to make reaction based menus with disnake

Overview

disnake-ext-menus

An experimental extension menu that makes working with reaction menus a bit easier.

Installing

python -m pip install -U disnake-ext-menus

Getting Started

To 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:

from disnake.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()

Now, within a command we just instantiate it and we start it like so:

@bot.command()
async def menu_example(ctx):
    m = MyMenu()
    await m.start(ctx)

If an error happens then an exception of type menus.MenuError is raised.

This second example shows a confirmation menu and how we can compose it and use it later:

from disnake.ext import menus

class Confirm(menus.Menu):
    def __init__(self, msg):
        super().__init__(timeout=30.0, delete_message_after=True)
        self.msg = msg
        self.result = None

    async def send_initial_message(self, ctx, channel):
        return await channel.send(self.msg)

    @menus.button('\N{WHITE HEAVY CHECK MARK}')
    async def do_confirm(self, payload):
        self.result = True
        self.stop()

    @menus.button('\N{CROSS MARK}')
    async def do_deny(self, payload):
        self.result = False
        self.stop()

    async def prompt(self, ctx):
        await self.start(ctx, wait=True)
        return self.result

Then when it comes time to use it we can do it like so:

@bot.command()
async def delete_things(ctx):
    confirm = await Confirm('Delete everything?').prompt(ctx)
    if confirm:
        await ctx.send('deleted...')

Pagination

The meat of the library is the Menu class but a MenuPages class is provided for the common use case of actually making a pagination session.

The MenuPages works similar to Menu except things are separated into a PageSource. The actual MenuPages rarely needs to be modified, instead we pass in a PageSource that 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 to itertools.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:

from disnake.ext import menus

class MySource(menus.ListPageSource):
    def __init__(self, data):
        super().__init__(data, per_page=4)

    async def format_page(self, menu, entries):
        offset = menu.current_page * self.per_page
        return '\n'.join(f'{i}. {v}' for i, v in enumerate(entries, start=offset))

# somewhere else:
pages = menus.MenuPages(source=MySource(range(1, 100)), clear_reactions_after=True)
await pages.start(ctx)

The format_page can return either a str for content, disnake.Embed for an embed, or a dict to pass into the kwargs of Message.edit.

Some more examples using GroupByPageSource:

from disnake.ext import menus

class Test:
    def __init__(self, key, value):
        self.key = key
        self.value = value

data = [
    Test(key=key, value=value)
    for key in ['test', 'other', 'okay']
    for value in range(20)
]

class Source(menus.GroupByPageSource):
    async def format_page(self, menu, entry):
        joined = '\n'.join(f'{i}. 
   
    {
    v.
    value
    }
   >' for i, v in enumerate(entry.items, start=1))
        return f'**{entry.key}**\n{joined}\nPage {menu.current_page + 1}/{self.get_max_pages()}'

pages = menus.MenuPages(source=Source(data, key=lambda t: t.key, per_page=12), clear_reactions_after=True)
await pages.start(ctx)

Another one showing AsyncIteratorPageSource:

from disnake.ext import menus

class Test:
    def __init__(self, value):
        self.value = value

    def __repr__(self):
        return f'
   
    {
    self.
    value
    }
   >'

async def generate(number):
    for i in range(number):
        yield Test(i)

class Source(menus.AsyncIteratorPageSource):
    def __init__(self):
        super().__init__(generate(9), per_page=4)

    async def format_page(self, menu, entries):
        start = menu.current_page * self.per_page
        return f'\n'.join(f'{i}. {v!r}' for i, v in enumerate(entries, start=start))

pages = menus.MenuPages(source=Source(), clear_reactions_after=True)
await pages.start(ctx)
You might also like...
A git extension for seeing your Cloud Build deployment

A git extension for seeing your Cloud Build deployment

This is a Fava extension to display a grouped portfolio view in Fava for a set of Beancount accounts.
This is a Fava extension to display a grouped portfolio view in Fava for a set of Beancount accounts.

Fava Portfolio Summary This is a Fava extension to display a grouped portfolio view in Fava for a set of Beancount accounts. It can also calculate MWR

Keyboard Layout Change - Extension for Ulauncher

Keyboard Layout Change - Extension for Ulauncher

Albert launcher extension for rolling dice.
Albert launcher extension for rolling dice.

dice-roll-albert-ext Extension for rolling dice in Albert launcher Installation Locate the modules directory in the Python extension data directory. T

A 3D Slicer Extension to view data from the flywheel heirarchy
A 3D Slicer Extension to view data from the flywheel heirarchy

flywheel-connect A 3D Slicer Extension to view, select, and download images from a Flywheel instance to 3D Slicer and storing Slicer outputs back to F

Functional collections extension functions for Python

pyfuncol pyfuncol Installation Usage API Documentation Compatibility Contributing License A Python functional collections library. It extends collecti

 Gaia: a chrome extension that curates environmental news of a company
Gaia: a chrome extension that curates environmental news of a company

Gaia - Gaia: Your Environment News Curator Call for Code 2021 Gaia: a chrome extension that curates environmental news of a company Explore the docs ยป

A ULauncher/Albert extension that supports currency, units and date time conversion, as well as a calculator that supports complex numbers and functions.
A ULauncher/Albert extension that supports currency, units and date time conversion, as well as a calculator that supports complex numbers and functions.

Ulauncher/Albert Calculate Anything Ulauncher/Albert Calculate Anything is an extension for Ulauncher and Albert to calculate things like currency, ti

An Embedded Linux Project Build and Compile Tool -- An Bitbake UI Extension
An Embedded Linux Project Build and Compile Tool -- An Bitbake UI Extension

Dianshao - An Embedded Linux Project Build and Compile Tool

Owner
null
Python / C++ based particle reaction-diffusion simulator

ReaDDy (Reaction Diffusion Dynamics) is an open source particle based reaction-diffusion simulator that can be configured and run via Python. Currentl

ReaDDy 46 Dec 9, 2022
A modern python module including many useful features that make discord bot programming extremely easy.

discord-super-utils Documentation Secondary Documentation A modern python module including many useful features that make discord bot programming extr

null 106 Dec 19, 2022
Module for remote in-memory Python package/module loading through HTTP/S

httpimport Python's missing feature! The feature has been suggested in Python Mailing List Remote, in-memory Python package/module importing through H

John Torakis 220 Dec 17, 2022
51AC8 is a stack based golfing / esolang that I am trying to make.

51AC8 is a stack based golfing / esolang that I am trying to make.

null 7 May 22, 2022
Example python package with pybind11 cpp extension

Developing C++ extension in Python using pybind11 This is a summary of the commands used in the tutorial.

null 55 Sep 4, 2022
A Python3 script to decode an encoded VBScript file, often seen with a .vbe file extension

vbe-decoder.py Decode one or multiple encoded VBScript files, often seen with a .vbe file extension. Usage usage: vbe-decoder.py [-h] [-o output] file

John Hammond 147 Nov 15, 2022
The purpose of this script is to bypass disablefund, provide some useful information, and dig the hook function of PHP extension.

The purpose of this script is to bypass disablefund, provide some useful information, and dig the hook function of PHP extension.

Firebasky 14 Aug 2, 2021
This is a vscode extension with a Virtual Assistant that you can play with when you are bored or you need help..

VS Code Virtual Assistant This is a vscode extension with a Virtual Assistant that you can play with when you are bored or you need help. Its currentl

Soham Ghugare 6 Aug 22, 2021
Broken Link Finder is a Burp Extension to detect broken links for a passive scanning domains and links.

Broken Link Finder Broken Link Finder is a Burp Extension to detect broken links for a passive scanning domains and links. Inspired by InitRoot's link

Red Section 10 Sep 11, 2021
An extension for Arma 3 that lets you write extensions in Python 3

An Arma 3 extension that lets you to write python extensions for Arma 3. And it's really simple and straightforward to use!

Lukasz Taczuk 48 Dec 18, 2022