a single-page result (max_pages < 2) suppressed the navigation row by dropping the whole view, which also discarded the consumer's custom per-page buttons. now: if the page carries custom buttons, keep the view and rebuild with update_buttons(nav=False) — nav items suppressed, custom buttons kept, and stop() NOT called so their callbacks still fire. a page with no custom buttons keeps the original drop-the-view behavior. verified by execution against real discord.py: single page + custom button -> start() -> callback FIRES on click (view kept, stop() not called); negative control on the old code drops the button entirely; the no-button single-page case is unregressed. Signed-off-by: disqualifier <dev@disqualifier.me> |
||
|---|---|---|
| src/dpy_paginator | ||
| .gitignore | ||
| pyproject.toml | ||
| README.md | ||
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.2
Direct:
pip install "dpy_paginator @ git+ssh://git@git.rethinkstudios.io/rethink-public/dpy_paginator.git@v0.1.2"
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.