Skip to main content

High-performance Discord framework inspired by Aiogram 3.x architecture

Project description

VaidCord Python SDK (vaidcord)

VaidCord is a Python Discord SDK built around a small set of composable pieces: Bot, Dispatcher, Router, filters, middleware, and FSM.

Current package status: Beta (0.1.0b4).

Why it exists

  • Predictable routing with feature-oriented routers.
  • Hierarchical dependency injection.
  • Filter-driven handlers that can also extract data.
  • Middleware for cross-cutting behavior.
  • Pluggable FSM storage for conversational workflows.
  • Mock utilities for fast, deterministic tests.

Installation

uv add vaidcord
uv add "vaidcord[redis]"
uv add "vaidcord[mongo]"
uv add "vaidcord[postgres]"
uv add "vaidcord[voice]"

Recommended entry points

  1. Start with examples/hello_echo_bot.py for the smallest runnable bot.
  2. Read docs/PYTHON_DRIVER.md for the architecture and runtime model.
  3. Browse examples/README.md for feature-focused examples.
  4. Use docs/OAUTH2.md and docs/APPLICATION_API.md when you need auth or application resources.
  5. Use docs/VOICE_AND_SLASH_COMMANDS.md for voice + slash command workflows.

Mental model

  • Bot is a facade/orchestrator that wires runtime + REST client + routers.
  • GatewayRuntime owns websocket lifecycle (connect/identify/heartbeat/dispatch loop).
  • APIClient owns Discord REST calls and delegates HTTP details to HTTPClient.
  • Dispatcher is the root router and runtime coordinator.
  • Router groups features into reusable modules.
  • Middleware wraps event handling.
  • Filters decide whether a handler runs and can inject data.
  • FSM stores scoped state for conversations and workflows.

Core patterns

  • Use dispatcher.provide("name", value) for global services.
  • Use router.provide("name", value) for feature-local services.
  • Use F for expressive filters like F.message.content.startswith("/start").
  • Use @router.on_message_state(...) when a flow depends on FSM state.
  • Use MockBot or MockDiscordServer for deterministic tests.

Sending messages

Use message-bound helpers inside message handlers:

@router.on_message()
async def echo(message: Message) -> None:
    await message.answer("pong")
    await message.reply("reply pong", mention_author=False)

Use Bot.send_message(channel_id, content, **kwargs) when you only have a channel id or when sending from service code outside a message handler.

Send DM to a user

Bot.send_dm(user_id, content, **kwargs) opens or reuses the DM channel via POST /users/@me/channels, then sends the message with the same payload options as send_message (embeds, components, etc.).

There is also an alias: send_message_to_user(...).

message = await bot.send_dm(
    user_id=123456789012345678,
    content="Hi from VaidCord!",
    embeds=[{"title": "DM"}],
)

See runnable example: examples/send_dm_to_user.py.

Filter composition semantics

  • Filter may return bool or dict.
  • dict means: filter passed and payload is injected into event.context["filter_data"] and handler kwargs (by parameter name).
  • A & B: both filters must pass; dict payloads from both sides are merged left-to-right.
  • A | B: first passing filter wins; payload from that branch is used.
@router.on_message((F.message.content.startswith("!admin")) & my_role_filter)
async def admin_handler(message: Message, role: str | None = None):
    ...

Runtime modes

await dp.start_polling(bot)
await dp.start_websocket(bot)
await dp.start_webhook(bot, drop_pending_updates=True)
await dp.start_polling_many([bot_a, bot_b])
await dp.start_webhook_many([bot_a, bot_b], drop_pending_updates=True)

Dispatcher() auto-registers FSM middleware. If you do not pass storage, it uses in-memory storage by default.

Support status

Area Status Notes
Gateway lifecycle supported Identify, heartbeat, reconnect-oriented runtime, typed common events.
Messages supported Send, reply, edit, delete, reactions, pins, polls, and typed message events.
Channels and threads partial Core channel, invite, permission overwrite, and thread helpers exist; keep checking Discord parity as new fields/routes land.
Guilds and users partial Common fetch/update/list helpers exist; advanced administration is still expanding.
Interactions partial Decorator-based slash/user/message command registration and command sync are available; continue validating edge-cases for large command trees.
OAuth2 supported URL, token, refresh, revoke, and userinfo helpers.
FSM supported Scoped storage with memory, SQLite, Redis, Mongo, and Postgres backends.
Mock server/testing supported Local mock server, mock bot, builders, and deterministic test helpers.
Multi-bot startup experimental start_polling_many and start_webhook_many are covered by regression tests, but large deployments should load-test their router/middleware mix.
Voice partial Voice gateway + UDP + speaking state + transport-level frame/file streaming helpers are available; full media pipeline/e2ee/codec tooling is still expanding.

Performance and memory notes

  • Prefer one Dispatcher with feature routers over duplicating large router trees per bot.
  • Use external FSM storage for long-lived or multi-process state; in-memory storage is fastest but process-local.
  • Keep filters cheap on high-volume routes. Expensive I/O belongs in handlers or middleware after narrow filters pass.
  • Avoid storing large Discord payloads in event.context; pass IDs or compact service objects instead.
  • For benchmarks, measure event propagation with representative middleware/filter counts and HTTP concurrency with real route mixes.
  • Contributors should avoid per-event introspection and avoid unnecessary object copies in parser, filter, and router hot paths.

Feature map

Typing guide

Router handlers and middleware are typed via vaidcord.typing protocols:

  • EventHandler: async def handler(event: Event, **kwargs: Any) -> object | None
  • Middleware: async def middleware(event: Event, next_handler: NextHandler) -> object | None
  • FilterDataMap: alias for filter payload (dict[str, Any]) injected into handler kwargs.
  • AbstractEventHandler / AbstractMiddleware: ABC-based option for class-style architecture.
  • DIEventCallable / DIWrapper: generic helpers (TypeVar, ParamSpec, Concatenate) for advanced wrappers.
from vaidcord.router import Router
from vaidcord.types import Message

router = Router()

@router.on_message()
async def echo(message: Message) -> None:
    await message.answer("pong")
from vaidcord.filters import F
from vaidcord.router import Router
from vaidcord.types import Message

router = Router()

@router.on_message(F.message.content.startswith("/set "))
async def set_value(message: Message, matched_text: str) -> None:
    # `matched_text` comes from filter-return payload
    await message.answer(f"Got: {matched_text}")
from vaidcord.router import Router
from vaidcord.typing import NextHandler
from vaidcord.types import Event

router = Router()

@router.middleware()
async def trace(event: Event, next_handler: NextHandler):
    print("before", event.type)
    result = await next_handler(event)
    print("after", event.type)
    return result
from vaidcord.typing import AbstractEventHandler
from vaidcord.types import Message

class PingHandler(AbstractEventHandler[Message]):
    async def __call__(self, message: Message, **kwargs: object) -> None:
        await message.answer("pong")

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

vaidcord-0.2.0.tar.gz (3.8 MB view details)

Uploaded Source

Built Distribution

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

vaidcord-0.2.0-py3-none-any.whl (201.4 kB view details)

Uploaded Python 3

File details

Details for the file vaidcord-0.2.0.tar.gz.

File metadata

  • Download URL: vaidcord-0.2.0.tar.gz
  • Upload date:
  • Size: 3.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for vaidcord-0.2.0.tar.gz
Algorithm Hash digest
SHA256 7fe2456a4d787ca6f3fc6a2cdc10159617b868e3ce62c8e080c009c128d38e66
MD5 d80a4fda84f9a95a51a243366a0e3ca1
BLAKE2b-256 5f3d6b846f6760e7a4732443538aabf55bc0986568ea712c49076917dd5885eb

See more details on using hashes here.

Provenance

The following attestation bundles were made for vaidcord-0.2.0.tar.gz:

Publisher: publish-pypi.yml on Vadim-Khristenko/vaidcord

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

File details

Details for the file vaidcord-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: vaidcord-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 201.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for vaidcord-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d68700b8fbd37c5745ae58fae56d1466ca03b19d18925304e0924df8f65aae19
MD5 c897615cde5654f43cdb4aa1d1d60415
BLAKE2b-256 2dfcd5e0a88df617d8a22bb07cc64080b663695479d63bcce0941bbfb66e7a27

See more details on using hashes here.

Provenance

The following attestation bundles were made for vaidcord-0.2.0-py3-none-any.whl:

Publisher: publish-pypi.yml on Vadim-Khristenko/vaidcord

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

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