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.1.0b4.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.1.0b4-py3-none-any.whl (159.7 kB view details)

Uploaded Python 3

File details

Details for the file vaidcord-0.1.0b4.tar.gz.

File metadata

  • Download URL: vaidcord-0.1.0b4.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.1.0b4.tar.gz
Algorithm Hash digest
SHA256 962cfd846551f019e9fb2fbdfe859e9a354adb8aed5cfb348f08aa3b928693f9
MD5 e34b1f6847510b241beb652ce83f8de9
BLAKE2b-256 496f05b76274ba90993e85fc95c1f49e580b585d17ce3ead0f5bb66f94e54401

See more details on using hashes here.

Provenance

The following attestation bundles were made for vaidcord-0.1.0b4.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.1.0b4-py3-none-any.whl.

File metadata

  • Download URL: vaidcord-0.1.0b4-py3-none-any.whl
  • Upload date:
  • Size: 159.7 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.1.0b4-py3-none-any.whl
Algorithm Hash digest
SHA256 5dac8fdca1ad93807aa357818628ce61af8da9985d17052da780cc3a8b0bb714
MD5 fff77b801b3235194f263e8fb16b2279
BLAKE2b-256 13c829101a13ac5e8d9637603ae43763397753044563c6d2f3cd4ef0814d23b5

See more details on using hashes here.

Provenance

The following attestation bundles were made for vaidcord-0.1.0b4-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