A Discord toolkit for building metadata browsers with embeds and button navigation.
Project description
discord-metadata
A Discord toolkit for building interactive metadata browsers with embeds and select-menu navigation.
Define your entities once and get Discord embeds with paginated sections, navigable links, and browsable search results. The Discord counterpart of rich-metadata.
Install
Requires Python 3.12+.
pip install discord-metadata
# or
uv add discord-metadata
Quick start
from discord_metadata import (
BaseNavigator,
DisplayEngine,
EntityDef,
HeaderField,
HeaderLink,
MetadataBot,
SectionDef,
SummaryField,
SyncAPI,
TableColumn,
)
# 1. Define entities
movie_def = EntityDef(
type_name="movie",
summary=[
SummaryField(key="title", bold=True),
SummaryField(key="year", prefix="(", transform=lambda v: f"{v})"),
],
header_fields=[
HeaderField("Director", key="director"),
HeaderField("Year", key="year"),
HeaderField("Genre", key="genre"),
],
sections=[
SectionDef(
"cast",
navigable=True,
columns=[
TableColumn("Actor", "name"),
TableColumn("Role", "role"),
],
),
SectionDef("synopsis"),
],
header_links=[
HeaderLink("Director: {director}", "person", ref_key="director_id"),
],
title_key="title",
color=0xE5A00D,
footer="Movie Database",
url_key="url",
)
# 2. Create engine and register definitions
engine = DisplayEngine()
engine.register(movie_def)
# 3. Wire up APIs and create bot
navigator = BaseNavigator(
engine,
apis={"movie": SyncAPI(MovieAPI())}, # or pass async APIs directly
)
bot = MetadataBot(navigator)
# 4. Register slash commands
@bot.tree.command(name="movie", description="Search for a movie")
async def cmd_movie(interaction, query: str):
await bot.navigator.search_and_navigate(interaction, query, ["movie"])
# 5. Run
bot.run_with_args("DISCORD_TOKEN")
See examples/movie_bot.py for a complete working example.
Concepts
EntityDef
Declares how an entity type is rendered as a Discord embed:
| Field | Purpose |
|---|---|
type_name |
Matches the _type key in data dicts |
summary |
One-line representation for search results and select menus |
header_fields |
Key-value fields shown in the embed header |
sections |
Named content blocks (text, tables, navigable lists) |
header_links |
Buttons that navigate to related entities |
image_url_key / thumbnail_url_key |
Embed image or thumbnail |
color / footer / url_key |
Embed appearance |
auto_full |
Show all sections at once (skip section menu) |
SectionDef
Each section can be:
- Text — a plain string value rendered in the embed description
- Table — a list of dicts rendered with
columns(list ofTableColumn) - Navigable — table rows become selectable items in the select menu
- Lazy — fetched on demand via the API's
get(ref, section=key) - Numbered — items prefixed with
1.,2., etc. (default:True)
DisplayEngine
Registry of entity definitions. Call engine.register(def1, def2, ...) to add definitions.
BaseNavigator
The interactive browser. Main entry points:
| Method | Description |
|---|---|
search_and_navigate(interaction, query, types) |
Search APIs, show results, navigate on selection |
browse(interaction, fetch_page, title=...) |
Paginated browsing with a fetch_page(start, count) callable |
browse_sources(interaction, sources) |
Let the user pick between multiple browsable sources |
navigate_entity(interaction, entity) |
Display an entity directly |
navigate_results(interaction, results) |
Show a result list |
SyncAPI
Wraps a synchronous API (with .get(ref) and .search(query) methods) for safe
use in async Discord handlers via asyncio.to_thread.
apis = {"book": SyncAPI(BookAPI(client))}
Async APIs can be passed directly without wrapping.
MetadataBot
A ready-made discord.Client subclass that handles:
- Command tree setup and guild sync
.envloading via python-dotenv (optional)--guild GUILD_IDCLI argument for instant command sync during development- Cleanup callback via
on_close
bot = MetadataBot(navigator, on_close=client.close)
bot.run_with_args("DISCORD_TOKEN")
Navigator options
BaseNavigator(
engine,
apis={...},
entity_ref_key="url", # key used to fetch entity details (default: "url")
ephemeral=False, # whether responses are ephemeral
placeholder="Browse...", # select menu placeholder text
search_kwargs={}, # extra kwargs passed to search methods
)
Paginated browsing
For APIs that return paginated results (discover, recent, upcoming, etc.),
use browse() with a fetch_page callable:
@bot.tree.command(name="recent")
async def cmd_recent(interaction):
await interaction.response.defer()
await navigator.browse(
interaction,
fetch_page=lambda start, count: api.fetch_page(start, count),
title="Recent releases",
)
fetch_page(start, count) must return (results: list[dict], total: int).
It can be sync or async.
Data format
Entity data is passed as plain dicts with a _type discriminator:
{
"_type": "movie",
"title": "Blade Runner",
"year": "1982",
"director": "Ridley Scott",
"cast": [
{"_type": "person", "name": "Harrison Ford", "role": "Deckard"},
],
}
Keys prefixed with _ (except _type) are internal and stripped from public output.
Development
git clone https://github.com/gabriel-jung/discord-metadata.git
cd discord-metadata
uv sync
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file discord_metadata-0.1.1.tar.gz.
File metadata
- Download URL: discord_metadata-0.1.1.tar.gz
- Upload date:
- Size: 14.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0bccda615512dfa846183b2ee9fd3ebf9b9702d349a27132ba0d186e84bc53f8
|
|
| MD5 |
e429c49f1301283859f2125a7e080f97
|
|
| BLAKE2b-256 |
aba369d9c3dcc0104cc3c509f71f80f8322b20ef6d1ca2a8dd645c5366a7fa56
|
File details
Details for the file discord_metadata-0.1.1-py3-none-any.whl.
File metadata
- Download URL: discord_metadata-0.1.1-py3-none-any.whl
- Upload date:
- Size: 15.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74ca49f14032c11f4637d20dd376b57038e0381d131febddbc4173343823351d
|
|
| MD5 |
b33788ae1339b37cc6825e043e8ddedb
|
|
| BLAKE2b-256 |
968300203f040592a9c8c47ed78348ebaf105720d0d2f126a0f832d81220f97e
|