A production-ready async Discord API wrapper for Python 3.11+
Project description
VeloxCord
VeloxCord is a Python library for creating Discord bots. It handles all the communication with Discord for you — you just write the logic.
Table of Contents
- Installation
- Your First Bot
- Intents
- Client
- Events
- Slash Commands
- Prefix Commands
- Command Groups
- Checks & Permissions
- Cooldowns
- Buttons & Select Menus
- Modals (Popups)
- Embeds
- Cache
- Error Handling
Installation
Open your terminal and run:
pip install veloxcord
Requirements:
- Python 3.11 or newer
- aiohttp (installed automatically)
Your First Bot
Here is the simplest possible bot. It replies "Pong!" when someone types !ping.
import veloxcord
# Step 1: set up intents (what events your bot wants to receive)
intents = veloxcord.Intents.default()
intents = veloxcord.Intents(
intents.value | veloxcord.Intents.MESSAGE_CONTENT
)
# Step 2: create the client
client = veloxcord.Client(intents=intents)
# Step 3: listen for messages
@client.on(veloxcord.EventType.MESSAGE_CREATE)
async def on_message(message: veloxcord.Message):
if message.content == "!ping":
await message.channel.send("Pong!")
# Step 4: run the bot with your token
client.run("YOUR_BOT_TOKEN")
Get your token from the Discord Developer Portal. Never share it with anyone.
If you already have an event loop running (for example inside another async program):
import asyncio
import veloxcord
client = veloxcord.Client(intents=veloxcord.Intents.default())
async def main():
await client.start("YOUR_BOT_TOKEN")
asyncio.run(main())
Intents
Intents tell Discord what kinds of events your bot wants to receive. Without the right intents, your bot will not see certain events at all.
# Default intents — good for most bots
intents = veloxcord.Intents.default()
# If you need to read message content (e.g. prefix commands or !ping)
# you must add MESSAGE_CONTENT like this:
intents = veloxcord.Intents.default()
intents = veloxcord.Intents(
intents.value | veloxcord.Intents.MESSAGE_CONTENT
)
# All intents at once (includes privileged ones)
intents = veloxcord.Intents.all()
# Build your own combination manually
intents = veloxcord.Intents(
veloxcord.Intents.GUILDS
| veloxcord.Intents.GUILD_MESSAGES
| veloxcord.Intents.MESSAGE_CONTENT
)
Intent List
| Intent | What it does |
|---|---|
GUILDS |
Server created, deleted, updated |
GUILD_MEMBERS ⚠️ |
Members joining or leaving |
GUILD_MESSAGES |
Messages sent in server channels |
MESSAGE_CONTENT ⚠️ |
Read the actual text of messages |
GUILD_MESSAGE_REACTIONS |
Reactions added or removed |
DIRECT_MESSAGES |
Direct messages sent to your bot |
GUILD_VOICE_STATES |
Users joining/leaving voice channels |
GUILD_PRESENCES ⚠️ |
User online/offline status |
GUILD_MODERATION |
Bans, timeouts, moderation actions |
GUILD_EMOJIS_AND_STICKERS |
Emoji and sticker changes |
GUILD_SCHEDULED_EVENTS |
Scheduled event updates |
⚠️ Privileged intents (
GUILD_MEMBERS,GUILD_PRESENCES,MESSAGE_CONTENT) must be manually enabled in the Discord Developer Portal under your app → Bot → Privileged Gateway Intents.
Client
The Client is the main object of your bot. Everything goes through it.
client = veloxcord.Client(
intents=intents, # required — your Intents object
prefix="!", # prefix for text commands, e.g. "!" or ["!", "?"]
case_insensitive=False, # if True, !Ping and !ping both work
owner_id=123456789, # your Discord user ID (for is_owner() check)
cache_config=None, # custom cache settings (see Cache section)
shard_ids=None, # for large bots with sharding
shard_count=None,
)
Properties
client.user # the bot's own User object (only available after READY fires)
client.latency # ping to Discord in seconds (multiply by 1000 for ms)
client.guilds # list of all servers the bot is in (from cache)
client.users # list of all cached users
client.is_ready # True once the bot has connected and received READY
client.is_closed # True if the connection has been closed
client.cache # direct access to the cache
Getting Objects
# Fast — reads from local cache, no network request
guild = client.get_guild(guild_id)
channel = client.get_channel(channel_id)
user = client.get_user(user_id)
message = client.get_message(message_id)
# Slower — makes an HTTP request to Discord
guild = await client.fetch_guild(guild_id)
channel = await client.fetch_channel(channel_id)
user = await client.fetch_user(user_id)
Syncing Slash Commands
After registering slash commands you need to sync them with Discord:
# Sync globally (can take up to 1 hour to appear everywhere)
await client.sync_commands()
# Sync to one specific server (appears instantly — great for testing)
await client.sync_commands(guild_id=123456789)
Changing Bot Status
await client.change_presence(
status=veloxcord.Status.ONLINE, # ONLINE, IDLE, DND, INVISIBLE
activity_name="with Python",
activity_type=veloxcord.ActivityType.PLAYING, # PLAYING, WATCHING, LISTENING, STREAMING
)
Starting and Stopping
client.run("TOKEN") # starts the bot (blocks until stopped)
await client.start("TOKEN") # async version
await client.close() # gracefully stop the bot
Events
Events are things that happen in Discord — a message is sent, someone joins a server, etc. You register functions that get called when an event happens.
# Basic event listener
@client.on(veloxcord.EventType.MESSAGE_CREATE)
async def on_message(message: veloxcord.Message):
print(f"{message.author.username} said: {message.content}")
# Run only the first time this event fires, then unregister automatically
@client.once(veloxcord.EventType.READY)
async def on_ready(user: veloxcord.User):
print(f"Bot is online as {user.username}")
await client.sync_commands()
# Priority: if multiple handlers listen to the same event,
# higher priority runs first (default is 0)
@client.on(veloxcord.EventType.GUILD_CREATE, priority=10)
async def on_guild_join(guild: veloxcord.Guild):
print(f"Joined server: {guild.name}")
Waiting for an Event
You can pause your code and wait until a specific event happens:
# Wait for the next message from a specific user (timeout after 30 seconds)
try:
msg = await client.wait_for(
veloxcord.EventType.MESSAGE_CREATE,
check=lambda m: m.author.id == 123456789,
timeout=30.0,
)
print(f"Got reply: {msg.content}")
except asyncio.TimeoutError:
print("User didn't reply in time")
Add/Remove Listeners Manually
async def my_handler(message):
print(message.content)
client.add_listener(my_handler, veloxcord.EventType.MESSAGE_CREATE)
client.remove_listener(my_handler, veloxcord.EventType.MESSAGE_CREATE)
All Main Event Types
| Event | What triggers it | Handler receives |
|---|---|---|
READY |
Bot connected to Discord | User |
MESSAGE_CREATE |
New message sent | Message |
MESSAGE_UPDATE |
Message edited | Message |
MESSAGE_DELETE |
Message deleted | Message or dict |
GUILD_CREATE |
Bot joins a server | Guild |
GUILD_UPDATE |
Server settings changed | Guild |
GUILD_DELETE |
Bot removed from server | Guild or dict |
GUILD_MEMBER_ADD |
User joined server | Member |
GUILD_MEMBER_REMOVE |
User left server | Member or User |
GUILD_MEMBER_UPDATE |
Member updated (role, nick) | Member |
CHANNEL_CREATE |
Channel created | Channel |
CHANNEL_UPDATE |
Channel updated | Channel |
CHANNEL_DELETE |
Channel deleted | Channel |
INTERACTION_CREATE |
Any interaction (slash, button, etc.) | Interaction |
ERROR |
Unhandled exception | Exception |
Slash Commands
Slash commands appear in Discord when users type /. They are the modern way to make bot commands.
@client.slash_command(name="ping", description="Check if the bot is alive")
async def ping(ctx: veloxcord.SlashContext):
ms = client.latency * 1000
await ctx.respond(f"Pong! Latency: {ms:.1f}ms")
Command Options
@client.slash_command(
name="greet",
description="Greet someone",
guild_ids=[123456789], # only show in these servers (optional)
default_member_permissions=None, # permission bitmask (None = everyone)
dm_permission=True, # allow in DMs
nsfw=False,
)
async def greet(ctx: veloxcord.SlashContext):
await ctx.respond("Hello!")
SlashContext — What You Can Do
async def my_command(ctx: veloxcord.SlashContext):
# Information about who ran the command
ctx.author # the user who used the command (Member or User)
ctx.user # same, but always a User object
ctx.guild_id # ID of the server (None if used in DMs)
ctx.channel_id # ID of the channel
ctx.guild # Guild object (None in DMs)
# Send a reply visible only to the user
await ctx.respond("Only you can see this!", ephemeral=True)
# Send a reply with an embed
await ctx.respond(embeds=[embed])
# Send a reply with buttons
await ctx.respond("Choose:", components=[row])
# If your command takes more than 3 seconds,
# call defer() first to prevent Discord from showing "failed"
await ctx.defer(ephemeral=True)
# ... do slow stuff ...
await ctx.followup("Done!")
# Open a modal (popup form)
await ctx.send_modal(modal)
Auto-defer: VeloxCord automatically defers your response if you take longer than 2 seconds, so you don't have to worry about it.
Prefix Commands
Prefix commands are triggered by a message starting with a symbol like !ping.
You must enable the
MESSAGE_CONTENTintent for these to work, and it must be enabled in the Developer Portal too.
intents = veloxcord.Intents.default()
intents = veloxcord.Intents(
intents.value | veloxcord.Intents.MESSAGE_CONTENT
)
client = veloxcord.Client(
intents=intents,
prefix="!", # or a list: ["!", "?", ">>"]
case_insensitive=True, # !Ping and !ping both work
)
@client.command(name="ping")
async def ping(ctx: veloxcord.PrefixContext):
await ctx.respond("Pong!")
# aliases let users type !echo instead of !say
@client.command(name="say", aliases=["echo"])
async def say(ctx: veloxcord.PrefixContext):
# ctx.args is a list of words after the command name
text = " ".join(ctx.args)
await ctx.respond(text)
PrefixContext — What You Can Do
async def my_cmd(ctx: veloxcord.PrefixContext):
ctx.message # the original Message object
ctx.prefix # which prefix was used (e.g. "!")
ctx.command_name # name of the command that was called
ctx.args # list of arguments as strings: ["hello", "world"]
ctx.author # who sent the message (Member or User)
ctx.channel # the channel it was sent in
ctx.guild_id # server ID (None in DMs)
await ctx.respond("Sends a reply to the message")
await ctx.send("Sends a new message to the channel")
Command Groups
Group related slash commands together under one name like /admin kick or /admin ban.
# Create a group
admin = veloxcord.SlashCommandGroup(
"admin",
"Admin commands",
guild_ids=[YOUR_GUILD_ID], # optional: only in specific servers
)
# Add subcommands to the group
@admin.sub_command("kick", "Kick a member")
async def admin_kick(ctx: veloxcord.SlashContext):
await ctx.respond("Member kicked!")
@admin.sub_command("ban", "Ban a member")
async def admin_ban(ctx: veloxcord.SlashContext):
await ctx.respond("Member banned!")
# You can also create subgroups: /admin config prefix
config = admin.sub_group("config", "Bot configuration")
@config.sub_command("prefix", "Change prefix")
async def config_prefix(ctx: veloxcord.SlashContext):
await ctx.respond("Prefix changed!")
# Register the group with the client
client._slash_commands["admin"] = admin
Checks & Permissions
Checks let you restrict who can use a command.
# Only works in servers (not DMs)
@client.slash_command(name="kick")
@veloxcord.guild_only()
async def kick(ctx):
await ctx.respond("Kicked!")
# Only works in DMs
@client.slash_command(name="private")
@veloxcord.dm_only()
async def private(ctx):
await ctx.respond("This is private!")
# User must have these permissions in the server
@client.slash_command(name="ban")
@veloxcord.require_permissions("ban_members")
async def ban(ctx):
await ctx.respond("Banned!")
# Only the bot owner can use this (set owner_id when creating Client)
@client.slash_command(name="shutdown")
@veloxcord.is_owner()
async def shutdown(ctx):
await ctx.respond("Shutting down...")
await client.close()
# Custom check — write any condition you want
def is_premium_user(ctx) -> bool:
PREMIUM_IDS = [111111111, 222222222]
return ctx.author.id in PREMIUM_IDS
@client.slash_command(name="premium")
@veloxcord.check(is_premium_user)
async def premium(ctx):
await ctx.respond("Welcome, premium user!")
# Limit how many people can run this at the same time
@client.slash_command(name="render")
@veloxcord.max_concurrency(1, veloxcord.BucketType.USER)
async def render(ctx):
await ctx.respond("Rendering...")
Cooldowns
Cooldowns prevent users from spamming commands.
from veloxcord import BucketType
# Allow 1 use per 24 hours per user
@client.slash_command(name="daily")
@veloxcord.cooldown(rate=1, per=86400, bucket=BucketType.USER)
async def daily(ctx: veloxcord.SlashContext):
await ctx.respond("Here is your daily reward!")
# Handle the error when someone is on cooldown
@client.on("COMMAND_ERROR")
async def on_command_error(exc, ctx):
if isinstance(exc, veloxcord.CommandOnCooldown):
await ctx.respond(
f"You're on cooldown! Try again in {exc.retry_after:.1f} seconds.",
ephemeral=True
)
Bucket Types
| BucketType | Cooldown applies per... |
|---|---|
USER |
Each user separately |
GUILD |
The whole server |
CHANNEL |
Each channel separately |
GLOBAL |
Everyone globally |
Buttons & Select Menus
Buttons and select menus are interactive components you attach to messages.
Buttons
@client.slash_command(name="vote", description="Cast a vote")
async def vote(ctx: veloxcord.SlashContext):
yes_btn = (
veloxcord.ButtonBuilder()
.success()
.label("Yes")
.custom_id("vote_yes")
.emoji("✅")
.build()
)
no_btn = (
veloxcord.ButtonBuilder()
.danger()
.label("No")
.custom_id("vote_no")
.emoji("❌")
.build()
)
row = (
veloxcord.ActionRowBuilder()
.add_button(yes_btn)
.add_button(no_btn)
.build()
)
await ctx.respond("Cast your vote:", components=[row])
@client.on_component("vote_yes")
async def on_vote_yes(ctx: veloxcord.ComponentContext):
await ctx.respond("You voted Yes!", ephemeral=True)
@client.on_component("vote_no")
async def on_vote_no(ctx: veloxcord.ComponentContext):
await ctx.respond("You voted No!", ephemeral=True)
Button styles:
| Method | Color | Use for |
|---|---|---|
.primary() |
Blue | Main action |
.secondary() |
Grey | Less important action |
.success() |
Green | Positive action |
.danger() |
Red | Destructive action |
.link("https://...") |
Grey + arrow | Opens a URL |
Other button options:
button = (
veloxcord.ButtonBuilder()
.primary()
.label("Click me")
.custom_id("my_btn")
.emoji("🔥") # add an emoji
.disabled() # make it unclickable (greyed out)
.build()
)
Handle Buttons by Pattern
If you have many buttons with similar IDs (like page_1, page_2), use a regex pattern:
@client.on_component(pattern=r"page_\d+")
async def handle_page(ctx: veloxcord.ComponentContext):
page_number = ctx.custom_id.split("_")[1]
await ctx.respond(f"Showing page {page_number}")
Select Menus (Dropdowns)
select = (
veloxcord.SelectMenuBuilder()
.custom_id("color_picker")
.placeholder("Choose a color...")
.min_values(1)
.max_values(1)
.option("Red", "red", description="Warm color", emoji="🔴")
.option("Green", "green", description="Nature color", emoji="🟢")
.option("Blue", "blue", description="Cool color", emoji="🔵", default=True)
.build()
)
select_row = veloxcord.ActionRowBuilder().add_select(select).build()
await ctx.respond("Pick a color:", components=[select_row])
@client.on_component("color_picker")
async def on_color_pick(ctx: veloxcord.ComponentContext):
await ctx.respond(f"You picked: {ctx.custom_id}", ephemeral=True)
ComponentContext — What You Can Do
async def handler(ctx: veloxcord.ComponentContext):
ctx.custom_id # the custom_id of the button/select that was clicked
ctx.author # who clicked it
ctx.guild_id # server ID
await ctx.respond("Reply to the interaction")
await ctx.defer()
await ctx.followup("Done!")
await ctx.update_message(content="Updated!") # edit the original message
Modals (Popups)
Modals are popup forms with text fields. They open when a user clicks a button or runs a command.
@client.slash_command(name="feedback", description="Send us your feedback")
async def feedback_cmd(ctx: veloxcord.SlashContext):
modal = (
veloxcord.ModalBuilder()
.custom_id("feedback_form")
.title("Send Feedback")
.short_text(
custom_id="subject",
label="Subject",
placeholder="What is this about?",
required=True,
min_length=5,
max_length=100,
)
.paragraph_text(
custom_id="body",
label="Your message",
placeholder="Tell us more...",
required=False,
)
.build()
)
await ctx.send_modal(modal)
@client.on_modal("feedback_form")
async def handle_feedback(ctx: veloxcord.ModalContext):
subject = ctx.get_value("subject")
body = ctx.get_value("body")
await ctx.respond(f"Thanks! We got your message about: **{subject}**", ephemeral=True)
ModalContext — What You Can Do
async def handler(ctx: veloxcord.ModalContext):
ctx.custom_id # the modal's custom_id
ctx.values # dict of all field values: {"subject": "...", "body": "..."}
ctx.get_value("field") # get one field's value by its custom_id
await ctx.respond("Thank you!")
await ctx.defer()
Embeds
Embeds are rich message cards with colors, fields, images, and more.
embed = (
veloxcord.EmbedBuilder()
.title("Server Stats")
.description("Here are the current server statistics.")
.color(0x5865F2) # hex color (Discord blurple)
.url("https://discord.com") # makes the title a clickable link
.author("My Bot", icon_url="https://i.imgur.com/abc.png")
.thumbnail("https://i.imgur.com/thumb.png") # small image top-right
.image("https://i.imgur.com/big.png") # large image at the bottom
.field("Members", "1,234", inline=True) # inline fields appear side by side
.field("Channels", "42", inline=True)
.field("About", "A longer field that takes full width.", inline=False)
.footer("Updated just now", icon_url="https://i.imgur.com/icon.png")
.timestamp() # adds current time to the footer
.build()
)
await ctx.respond(embeds=[embed])
Cache
VeloxCord automatically caches Discord objects so your bot doesn't make an HTTP request every time it needs a guild or user.
cache_config = veloxcord.CacheConfig(
guild_max_size=1024, # max guilds to store
channel_max_size=4096,
member_max_size=65536,
message_max_size=1024,
user_max_size=65536,
# How long to keep objects (seconds). None = keep forever.
member_ttl=3600.0, # drop members from cache after 1 hour
message_ttl=1800.0, # drop messages from cache after 30 minutes
user_ttl=3600.0,
disable_message_cache=False,
)
client = veloxcord.Client(intents=intents, cache_config=cache_config)
Reading from Cache
store = client.cache
guild = store.get_guild(guild_id)
channel = store.get_channel(channel_id)
user = store.get_user(user_id)
message = store.get_message(message_id)
member = store.get_member(guild_id, user_id)
role = store.get_role(role_id)
emoji = store.get_emoji(emoji_id)
# All objects as dicts
store.guilds # dict[int, Guild]
store.channels # dict[int, Channel]
store.users # dict[int, User]
store.messages # dict[int, Message]
Error Handling
Global Error Listener
@client.on("ERROR")
async def on_error(exc: Exception):
if isinstance(exc, veloxcord.RateLimited):
print(f"Being rate limited! Wait {exc.retry_after} seconds.")
elif isinstance(exc, veloxcord.Forbidden):
print("Bot is missing permissions.")
elif isinstance(exc, veloxcord.NotFound):
print("Resource not found.")
else:
raise exc
Command Error Listener
@client.on("COMMAND_ERROR")
async def on_command_error(exc: Exception, ctx):
if isinstance(exc, veloxcord.CommandOnCooldown):
await ctx.respond(f"Slow down! Try again in {exc.retry_after:.1f}s.", ephemeral=True)
elif isinstance(exc, veloxcord.MissingPermissions):
missing = ", ".join(exc.missing)
await ctx.respond(f"You need these permissions: {missing}", ephemeral=True)
elif isinstance(exc, veloxcord.CheckFailure):
await ctx.respond(str(exc), ephemeral=True)
Try/Except for HTTP Calls
try:
guild = await client.fetch_guild(123456789)
except veloxcord.NotFound:
print("That server doesn't exist or the bot isn't in it.")
except veloxcord.Forbidden:
print("Bot doesn't have access to that server.")
except veloxcord.HTTPException as e:
print(f"Something went wrong: HTTP {e.status} — {e.message}")
Exception List
VeloxException ← base for all errors
├── HTTPException ← Discord API returned an error
│ ├── BadRequest (400) ← your request was malformed
│ ├── Unauthorized (401) ← invalid token
│ ├── Forbidden (403) ← bot lacks permissions
│ ├── NotFound (404) ← resource doesn't exist
│ ├── RateLimited (429) ← too many requests
│ └── ServerError (5xx) ← Discord is having issues
├── GatewayException ← WebSocket connection error
│ ├── AuthenticationFailed ← bad token on connect
│ ├── DisallowedIntents ← intent not enabled in portal
│ ├── ShardingRequired ← bot is too large, needs sharding
│ └── InvalidShard ← shard config is wrong
└── CommandException ← command-related errors
├── CommandNotFound ← no command with that name
├── CommandOnCooldown ← user must wait
├── MissingPermissions ← user lacks required perms
├── BotMissingPermissions ← bot lacks required perms
├── CheckFailure ← custom check returned False
└── InvalidArgument ← wrong argument type/value
License
MIT — see LICENSE.
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 veloxcord-1.0.4.tar.gz.
File metadata
- Download URL: veloxcord-1.0.4.tar.gz
- Upload date:
- Size: 82.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e766802e1c78006f7de3928ded2aa203fbab954c6afbe57b6e77449e69ea27a
|
|
| MD5 |
d9f955198b696bc3b89572114fa28805
|
|
| BLAKE2b-256 |
e42993c679e1a4bc8e73e3ba2e4e1abeb44866f083a81a3c98ff2617bb37be7c
|
File details
Details for the file veloxcord-1.0.4-py3-none-any.whl.
File metadata
- Download URL: veloxcord-1.0.4-py3-none-any.whl
- Upload date:
- Size: 100.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a531307bc05e3017dd7206d8ae6d41b1bb0454fa0eea115e7798a95de07c0e65
|
|
| MD5 |
c24f5d53f5014d9c1e6ddf54a05c9dde
|
|
| BLAKE2b-256 |
832156893128ba45c2aba01b2d63313451b040a6fa9d74ecffaa3dce074b1d98
|