Files
dpy_paginator/README.md
T
dsql bba01fa931 refactor: rename ButtonPaginator -> DPYPaginator (keep ButtonPaginator as alias)
the paginator class is now DPYPaginator, matching the package name (dpy_paginator),
for naming consistency with the suite. ButtonPaginator is kept as a back-compat alias
(ButtonPaginator = DPYPaginator), so existing imports and call sites keep working
unchanged. both names are exported in __all__. mirrors the suite's alias convention
(aioweb Response/aiowebResponse, envelope_crypto EnvelopeCrypto/PCICrypto).

verified against discord.py 2.7.1: both names resolve to the same class, construction
via either name works, the empty-pages ValueError message updated. v0.1.6.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-02 17:50:24 -04:00

158 lines
5.7 KiB
Markdown

# 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.6
```
Direct:
```bash
pip install "dpy_paginator @ git+ssh://git@git.rethinkstudios.io/rethink-public/dpy_paginator.git@v0.1.6"
```
Requires `discord.py` (pulled transitively).
Drop the `@v0.1.6` suffix from the line above to install the latest unpinned.
## Basic usage
The paginator class is `DPYPaginator`. It is also exported as `ButtonPaginator` (a
back-compat alias) — both names refer to the same class, so either import works.
Plain pages — just navigation:
```python
from dpy_paginator import DPYPaginator # or: from dpy_paginator import ButtonPaginator
pages = [discord.Embed(title=f"Page {i}") for i in range(5)]
await DPYPaginator(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:
```python
DPYPaginator(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. Both `file` and
`files` accept a `discord.Attachment` and convert it via `to_file()` automatically —
you never need to convert an attachment before passing it in.
File pages are safe to navigate back to. discord.py closes a `discord.File`'s
underlying handle after every send/edit, so the paginator never resends your File
object directly — it rebuilds a fresh `discord.File` from the same source (path or
buffer) on every render, so `pages` can hold a `discord.File` once and be paged
back and forth indefinitely.
## 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.
```python
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 = DPYPaginator(
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.
Omit `cache` (or pass `None` / `[]`) and the button never appears; a non-empty cache
must have one entry per page or construction raises.
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.
```python
pages, cache = [], []
for group in groups:
pages.append(build_embed(group))
cache.append(" ".join(f"<@{uid}>" for uid in group["user_ids"]))
await DPYPaginator(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 content
- `cache` — optional per-page cache strings (enables the cache button)
- `author_id` — restrict interaction to this user
- `timeout` — seconds before the view stops (default 180)
- `delete_message_after` — delete the message on timeout
- `per_page` — entries per page (default 1)
- `mentions_allowed``AllowedMentions` for sends (default `.all()`)
- `ephemeral` — send/edit ephemerally
- `page_text` — format string for the jump button label
- `emojis` — 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
Releases are tagged `vX.Y.Z`. The install line above pins a release; drop the `@vX.Y.Z` suffix to install the latest unpinned. Pin deliberately for reproducible installs.