Skip to main content

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; navigable rows become read-only)

SectionDef

Each section can be:

  • Text — a plain string value rendered in the embed description
  • Table — a list of dicts rendered with columns (list of TableColumn)
  • Navigable — table rows become selectable items in the select menu
  • Lazy — fetched on demand via a lazy_fetchers={(type, section_key): fn} mapping passed to BaseNavigator (sync or async; sync receives the unwrapped API)
  • 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
  • .env loading via python-dotenv (optional)
  • --guild GUILD_ID CLI 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")
    lazy_fetchers={             # optional: (entity_type, section_key) -> fn(api, entity)
        ("movie", "synopsis"): lambda api, entity: api.fetch_synopsis(entity["id"]),
    },
    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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

discord_metadata-0.1.3.tar.gz (14.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

discord_metadata-0.1.3-py3-none-any.whl (16.4 kB view details)

Uploaded Python 3

File details

Details for the file discord_metadata-0.1.3.tar.gz.

File metadata

  • Download URL: discord_metadata-0.1.3.tar.gz
  • Upload date:
  • Size: 14.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for discord_metadata-0.1.3.tar.gz
Algorithm Hash digest
SHA256 5ff8aedb8099d7363794ae6ee66281abb5478eacfe78af77c72c67df3cfc828a
MD5 8cd45e130355aa51cd04198447923576
BLAKE2b-256 afdc9a87714a596012c05d0c45e78d550a88efbea1cd52d75a1107a8543f7646

See more details on using hashes here.

Provenance

The following attestation bundles were made for discord_metadata-0.1.3.tar.gz:

Publisher: publish.yml on gabriel-jung/discord-metadata

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file discord_metadata-0.1.3-py3-none-any.whl.

File metadata

File hashes

Hashes for discord_metadata-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 6567bc85d26262861f271e471b6c5b8e62ab53b927d9714e0682601aa99cb102
MD5 e404c7b6eeef3401523816043137dd84
BLAKE2b-256 6aa8e6a4084c569c6063bf82f141fae2b049855eb292c1a12e5e484e8c27a93c

See more details on using hashes here.

Provenance

The following attestation bundles were made for discord_metadata-0.1.3-py3-none-any.whl:

Publisher: publish.yml on gabriel-jung/discord-metadata

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page