Skip to main content

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 of TableColumn)
  • 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
  • .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")
    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


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.0.tar.gz (14.3 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.0-py3-none-any.whl (16.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: discord_metadata-0.1.0.tar.gz
  • Upload date:
  • Size: 14.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for discord_metadata-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9cb0e734c0c90cb660e9722abd12fa4cba43ac5d721372bfb2f78f845267a3c6
MD5 01ed6e28f6e9bb29c176d3d9139f9b0c
BLAKE2b-256 7b4d9340666d7f79e2bc2ed72bd9e187b165c4447c730249d3495a81d507d34c

See more details on using hashes here.

File details

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

File metadata

  • Download URL: discord_metadata-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 16.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for discord_metadata-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 28333e3bf8f9405a25abbc5b104253bc1d665c9c3681b4cb79932ccaeb70f81c
MD5 e9d0d6a1585801b4072976c5ca52ea55
BLAKE2b-256 f8138bc579632800080d62350f57837f542c5124b5dc7bb66e70a6fb52dc4e88

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page