get_page() out-of-range/negative wrap reset to page 0 but returned pages[0] (a single item) even with per_page>1, mis-shaping the page downstream; it now reroutes through get_page(0) so the normal slice logic applies. cache shorter than max_pages now raises ValueError at construction instead of an IndexError when the cache button is clicked on a later page. Signed-off-by: disqualifier <dev@disqualifier.me>
4.7 KiB
dpy_paginator
Button-navigated paginator for discord.py. A discord.ui.View that paginates mixed
content (strings, embeds, files, attachments, or dicts of mixed content with custom
buttons) behind previous / jump / next navigation, with an optional cache button.
Install
requirements.txt:
dpy_paginator @ git+ssh://git@git.rethinkstudios.io/rethink-public/dpy_paginator.git@v0.1.1
Direct:
pip install "dpy_paginator @ git+ssh://git@git.rethinkstudios.io/rethink-public/dpy_paginator.git@v0.1.1"
Requires discord.py (pulled transitively).
Basic usage
Plain pages — just navigation:
from dpy_paginator import ButtonPaginator
pages = [discord.Embed(title=f"Page {i}") for i in range(5)]
await ButtonPaginator(pages, author_id=ctx.author.id).start(ctx)
start() accepts an Interaction or any Messageable (a Context, channel, etc.).
Emojis
Navigation uses plain Unicode by default — no setup, no emoji upload required. Pass
emojis= to override with custom application/guild emojis the bot can use:
ButtonPaginator(pages, emojis={
"previous": "<:icon_back:123...>",
"next": "<:icon_next:123...>",
"cache": "<:icon_cache:123...>",
})
Unset keys fall back to the Unicode defaults.
Page types
A page may be a str, discord.Embed, discord.File/Attachment, a sequence of
those, or a dict. A dict page can carry content, embed/embeds,
file/files, and a buttons list of custom button configs.
Custom per-page buttons
Each page can declare its own buttons. They render alongside the navigation row and are rebuilt from the page dict on every render — so mutating a page's button config and re-rendering updates the button live.
async def callback_reorder(interaction, button, data, paginator):
await interaction.response.send_message(f"Reordering {data['email']}", ephemeral=True)
pages = []
for session in sessions:
pages.append({
"content": session["_id"],
"embed": build_embed(session),
"buttons": [
{
"label": "Reorder",
"style": discord.ButtonStyle.green,
"emoji": some_emoji, # optional, your own emoji
"disabled": False,
"data": {"email": session["_id"]},
"callback": callback_reorder,
}
],
})
paginator = ButtonPaginator(
pages, cache=None, timeout=900, delete_message_after=True,
mentions_allowed=discord.AllowedMentions.none(), ephemeral=True,
page_text="Session {} of {}",
)
await paginator.start(interaction)
Button config keys: label, style, emoji, disabled, data (arbitrary, passed
to the callback), callback. The callback signature is
async def cb(interaction, button, data, paginator). With no callback, the button
echoes its data.
Cache button (mention priming)
Discord clients render <@id> as a raw id until the user object is cached locally.
The cache button fixes that: pass cache=[...] with one entry per page, where each
entry is a string of the user mentions on that page ("<@111> <@222> <@333>").
When clicked, the button posts the page's mentions in a throwaway ephemeral message
(rendering the <@id> tags primes the viewer's client — allowed_mentions is
none() so no actual ping fires), waits cache_sleep seconds (default 1.0) for the
client to resolve them, then re-renders the current page — so the mentions now
display as names without the user having to flip pages manually.
pages, cache = [], []
for group in groups:
pages.append(build_embed(group))
cache.append(" ".join(f"<@{uid}>" for uid in group["user_ids"]))
await ButtonPaginator(pages, cache=cache, cache_sleep=1.0).start(ctx)
Omit cache or pass None/[] and the button never appears. When set, cache
must have one entry per rendered page — a shorter cache raises ValueError at
construction rather than failing with an IndexError mid-navigation.
Constructor options
pages— sequence of page contentcache— optional per-page cache strings (enables the cache button)author_id— restrict interaction to this usertimeout— seconds before the view stops (default 180)delete_message_after— delete the message on timeoutper_page— entries per page (default 1)mentions_allowed—AllowedMentionsfor sends (default.all())ephemeral— send/edit ephemerallypage_text— format string for the jump button labelemojis— override navigation emojis
Subclassing
Override format_page(page) to transform pages before rendering (e.g. wrap raw data
in an embed). It may be sync or async.
Versioning
Tagged vX.Y.Z. Pin the tag in requirements.txt.