A Python wrapper for the Discord API forked from discord.py
—
Traditional text-based command system with the commands extension, providing prefix commands, command groups, converters, and checks for comprehensive bot functionality.
Enhanced client with command processing capabilities and prefix-based command handling.
import nextcord
from nextcord.ext import commands
from nextcord.ext.commands import Bot, AutoShardedBot, Context
from typing import Optional, List, Union, Callable, Any
class Bot(commands.Bot):
"""A subclass of nextcord.Client that has command functionality.
This allows for a prefix-based command system alongside application commands.
Attributes
----------
command_prefix: Union[str, List[str], Callable]
The prefix(es) that the bot will respond to.
case_insensitive: bool
Whether commands are case insensitive.
description: Optional[str]
The bot's description.
help_command: Optional[HelpCommand]
The help command implementation.
owner_id: Optional[int]
The bot owner's user ID.
owner_ids: Optional[Set[int]]
Set of bot owner user IDs.
"""
def __init__(
self,
command_prefix: Union[str, List[str], Callable],
*,
help_command: Optional[commands.HelpCommand] = commands.DefaultHelpCommand(),
description: Optional[str] = None,
intents: nextcord.Intents = nextcord.Intents.default(),
case_insensitive: bool = False,
strip_after_prefix: bool = False,
**options
):
"""Initialize the bot.
Parameters
----------
command_prefix: Union[str, List[str], Callable]
The prefix that will trigger bot commands.
help_command: Optional[commands.HelpCommand]
The help command implementation. Pass None to disable.
description: Optional[str]
A description for the bot.
intents: nextcord.Intents
Gateway intents for the bot.
case_insensitive: bool
Whether commands should be case insensitive.
strip_after_prefix: bool
Whether to strip whitespace after the prefix.
**options
Additional options passed to the Client constructor.
"""
...
async def get_prefix(self, message: nextcord.Message) -> Union[str, List[str]]:
"""Get the prefix for a message.
This can be overridden for dynamic prefixes.
Parameters
----------
message: nextcord.Message
The message to get the prefix for.
Returns
-------
Union[str, List[str]]
The prefix(es) for this message.
"""
...
async def get_context(
self,
message: nextcord.Message,
*,
cls: type = Context
) -> Context:
"""Get the command context from a message.
Parameters
----------
message: nextcord.Message
The message to create context from.
cls: type
The context class to use.
Returns
-------
Context
The command context.
"""
...
# Basic bot setup
bot = commands.Bot(
command_prefix='!',
description='A helpful bot',
intents=nextcord.Intents.default(),
case_insensitive=True
)
# Dynamic prefix example
async def get_prefix(bot, message):
"""Dynamic prefix function."""
# Default prefixes
prefixes = ['!', '?']
# Add custom guild prefixes (from database)
if message.guild:
# This would typically query a database
guild_prefix = get_guild_prefix(message.guild.id) # Implement this
if guild_prefix:
prefixes.append(guild_prefix)
# Allow bot mention as prefix
return commands.when_mentioned_or(*prefixes)(bot, message)
# Bot with dynamic prefix
bot = commands.Bot(command_prefix=get_prefix)
@bot.event
async def on_ready():
"""Bot ready event."""
print(f'{bot.user} has connected to Discord!')
print(f'Bot is in {len(bot.guilds)} guilds')
@bot.event
async def on_message(message):
"""Process messages for commands."""
# Ignore bot messages
if message.author.bot:
return
# Process commands
await bot.process_commands(message)Individual command definitions with parameters, converters, and error handling.
def command(
name: Optional[str] = None,
*,
cls: Optional[type] = None,
**attrs
) -> Callable:
"""Decorator to create a command.
Parameters
----------
name: Optional[str]
The name of the command. If not given, uses the function name.
cls: Optional[type]
The class to construct the command with.
**attrs
Additional attributes for the command.
"""
...
# Basic command examples
@bot.command()
async def ping(ctx):
"""Check the bot's latency."""
latency = round(bot.latency * 1000)
await ctx.send(f'Pong! {latency}ms')
@bot.command(name='hello', aliases=['hi', 'hey'])
async def greet(ctx, *, name: str = None):
"""Greet a user.
Parameters
----------
ctx: commands.Context
The command context.
name: str, optional
The name to greet. If not provided, greets the command author.
"""
target = name or ctx.author.display_name
await ctx.send(f'Hello, {target}!')
@bot.command()
async def userinfo(ctx, member: nextcord.Member = None):
"""Get information about a user.
Parameters
----------
ctx: commands.Context
The command context.
member: nextcord.Member, optional
The member to get information about. Defaults to command author.
"""
user = member or ctx.author
embed = nextcord.Embed(
title=f"User Info: {user.display_name}",
color=user.color or nextcord.Color.blue()
)
embed.set_thumbnail(url=user.display_avatar.url)
embed.add_field(name="Username", value=str(user), inline=True)
embed.add_field(name="ID", value=user.id, inline=True)
embed.add_field(name="Joined", value=user.joined_at.strftime("%Y-%m-%d") if user.joined_at else "Unknown", inline=True)
await ctx.send(embed=embed)
# Command with multiple parameters
@bot.command()
async def ban(ctx, member: nextcord.Member, *, reason: str = "No reason provided"):
"""Ban a member from the server.
Parameters
----------
ctx: commands.Context
The command context.
member: nextcord.Member
The member to ban.
reason: str
The reason for the ban.
"""
# Check permissions
if not ctx.author.guild_permissions.ban_members:
await ctx.send("❌ You don't have permission to ban members.")
return
try:
await member.ban(reason=f"{reason} | Banned by {ctx.author}")
await ctx.send(f"✅ Banned {member.mention} for: {reason}")
except nextcord.Forbidden:
await ctx.send("❌ I don't have permission to ban this member.")
except nextcord.HTTPException:
await ctx.send("❌ Failed to ban member.")
# Command with converters
@bot.command()
async def role_info(ctx, role: nextcord.Role):
"""Get information about a role.
Parameters
----------
ctx: commands.Context
The command context.
role: nextcord.Role
The role to get information about.
"""
embed = nextcord.Embed(
title=f"Role Info: {role.name}",
color=role.color
)
embed.add_field(name="ID", value=role.id, inline=True)
embed.add_field(name="Position", value=role.position, inline=True)
embed.add_field(name="Members", value=len(role.members), inline=True)
embed.add_field(name="Mentionable", value="Yes" if role.mentionable else "No", inline=True)
embed.add_field(name="Hoisted", value="Yes" if role.hoist else "No", inline=True)
embed.add_field(name="Managed", value="Yes" if role.managed else "No", inline=True)
if role.permissions.administrator:
embed.add_field(name="⚠️ Warning", value="This role has Administrator permission!", inline=False)
await ctx.send(embed=embed)class Context:
"""The context for a command.
Provides information about the command invocation and convenience methods.
Attributes
----------
message: nextcord.Message
The message that triggered the command.
bot: Bot
The bot instance.
args: List[Any]
The arguments passed to the command.
kwargs: Dict[str, Any]
The keyword arguments passed to the command.
prefix: str
The prefix used to invoke the command.
command: Command
The command that was invoked.
invoked_with: str
The alias used to invoke the command.
invoked_parents: List[str]
The parent commands used to invoke a subcommand.
invoked_subcommand: Optional[Command]
The subcommand that was invoked.
subcommand_passed: Optional[str]
The string passed after a group command.
guild: Optional[nextcord.Guild]
The guild the command was used in.
channel: Union[nextcord.abc.Messageable]
The channel the command was used in.
author: Union[nextcord.Member, nextcord.User]
The user who invoked the command.
me: Union[nextcord.Member, nextcord.ClientUser]
The bot's member object in the guild.
"""
async def send(
self,
content: Optional[str] = None,
*,
tts: bool = False,
embed: Optional[nextcord.Embed] = None,
embeds: Optional[List[nextcord.Embed]] = None,
file: Optional[nextcord.File] = None,
files: Optional[List[nextcord.File]] = None,
stickers: Optional[List[nextcord.GuildSticker]] = None,
delete_after: Optional[float] = None,
nonce: Optional[Union[str, int]] = None,
allowed_mentions: Optional[nextcord.AllowedMentions] = None,
reference: Optional[Union[nextcord.Message, nextcord.MessageReference]] = None,
mention_author: Optional[bool] = None,
view: Optional[nextcord.ui.View] = None,
suppress_embeds: bool = False,
) -> nextcord.Message:
"""Send a message to the channel.
This is a shortcut for ctx.channel.send().
Parameters are the same as nextcord.abc.Messageable.send().
Returns
-------
nextcord.Message
The message that was sent.
"""
...
async def reply(
self,
content: Optional[str] = None,
**kwargs
) -> nextcord.Message:
"""Reply to the message that invoked the command.
Parameters are the same as ctx.send().
Returns
-------
nextcord.Message
The message that was sent.
"""
...
def typing(self):
"""Return a typing context manager."""
return self.channel.typing()
@property
def valid(self) -> bool:
"""bool: Whether the context is valid for command processing."""
...
@property
def clean_prefix(self) -> str:
"""str: The cleaned up invoke prefix."""
...
@property
def cog(self) -> Optional["Cog"]:
"""Optional[Cog]: The cog the command belongs to."""
...
# Context usage examples
@bot.command()
async def ctx_demo(ctx):
"""Demonstrate context usage."""
embed = nextcord.Embed(title="Context Information")
embed.add_field(name="Author", value=ctx.author.mention, inline=True)
embed.add_field(name="Channel", value=ctx.channel.mention, inline=True)
embed.add_field(name="Guild", value=ctx.guild.name if ctx.guild else "DM", inline=True)
embed.add_field(name="Prefix", value=ctx.prefix, inline=True)
embed.add_field(name="Command", value=ctx.command.name, inline=True)
embed.add_field(name="Invoked With", value=ctx.invoked_with, inline=True)
await ctx.send(embed=embed)
@bot.command()
async def slow_command(ctx):
"""A command that takes some time to process."""
async with ctx.typing():
# Simulate slow processing
import asyncio
await asyncio.sleep(3)
await ctx.send("Processing complete!")
@bot.command()
async def reply_demo(ctx):
"""Demonstrate reply functionality."""
await ctx.reply("This is a reply to your command!")
# Custom context class
class CustomContext(commands.Context):
"""Custom context with additional features."""
async def send_success(self, message: str):
"""Send a success message."""
embed = nextcord.Embed(
title="✅ Success",
description=message,
color=nextcord.Color.green()
)
await self.send(embed=embed)
async def send_error(self, message: str):
"""Send an error message."""
embed = nextcord.Embed(
title="❌ Error",
description=message,
color=nextcord.Color.red()
)
await self.send(embed=embed)
async def confirm(self, message: str, timeout: float = 30.0) -> bool:
"""Ask for user confirmation."""
embed = nextcord.Embed(
title="Confirmation Required",
description=f"{message}\n\nReact with ✅ to confirm or ❌ to cancel.",
color=nextcord.Color.orange()
)
msg = await self.send(embed=embed)
await msg.add_reaction("✅")
await msg.add_reaction("❌")
def check(reaction, user):
return (
user == self.author and
reaction.message.id == msg.id and
str(reaction.emoji) in ["✅", "❌"]
)
try:
reaction, user = await self.bot.wait_for('reaction_add', check=check, timeout=timeout)
return str(reaction.emoji) == "✅"
except asyncio.TimeoutError:
await msg.edit(embed=nextcord.Embed(
title="Confirmation Timed Out",
description="No response received within 30 seconds.",
color=nextcord.Color.red()
))
return False
# Use custom context
async def get_context(message, *, cls=CustomContext):
return await super(Bot, bot).get_context(message, cls=cls)
bot.get_context = get_context
@bot.command()
async def delete_all_messages(ctx):
"""Delete all messages (with confirmation)."""
confirmed = await ctx.confirm("Are you sure you want to delete all messages? This cannot be undone!")
if confirmed:
# Perform the dangerous action
await ctx.send_success("Messages deleted successfully!")
else:
await ctx.send("Operation cancelled.")Hierarchical command organization with subcommands and groups.
def group(
name: Optional[str] = None,
*,
cls: Optional[type] = None,
invoke_without_command: bool = False,
**attrs
) -> Callable:
"""Decorator to create a command group.
Parameters
----------
name: Optional[str]
The name of the group.
cls: Optional[type]
The class to construct the group with.
invoke_without_command: bool
Whether the group can be invoked without a subcommand.
**attrs
Additional attributes for the group.
"""
...
class Group(commands.Command):
"""A command that can contain subcommands.
Attributes
----------
invoke_without_command: bool
Whether this group can be invoked without a subcommand.
commands: Dict[str, Command]
The subcommands in this group.
"""
def add_command(self, command: commands.Command) -> None:
"""Add a subcommand to this group."""
...
def remove_command(self, name: str) -> Optional[commands.Command]:
"""Remove a subcommand from this group."""
...
def get_command(self, name: str) -> Optional[commands.Command]:
"""Get a subcommand by name."""
...
# Group command examples
@bot.group(invoke_without_command=True)
async def config(ctx):
"""Server configuration commands.
Use subcommands to configure various aspects of the server.
"""
if ctx.invoked_subcommand is None:
embed = nextcord.Embed(
title="Server Configuration",
description="Available configuration options:",
color=nextcord.Color.blue()
)
embed.add_field(
name="Subcommands",
value=(
"`!config prefix <new_prefix>` - Change bot prefix\n"
"`!config welcome <channel>` - Set welcome channel\n"
"`!config autorole <role>` - Set auto role for new members\n"
"`!config logs <channel>` - Set log channel"
),
inline=False
)
await ctx.send(embed=embed)
@config.command()
async def prefix(ctx, new_prefix: str):
"""Change the bot prefix for this server."""
if not ctx.author.guild_permissions.manage_guild:
await ctx.send("❌ You need Manage Server permission to change the prefix.")
return
if len(new_prefix) > 5:
await ctx.send("❌ Prefix must be 5 characters or less.")
return
# Save to database (implement this)
save_guild_prefix(ctx.guild.id, new_prefix)
await ctx.send(f"✅ Bot prefix changed to `{new_prefix}`")
@config.command()
async def welcome(ctx, channel: nextcord.TextChannel):
"""Set the welcome channel for new members."""
if not ctx.author.guild_permissions.manage_guild:
await ctx.send("❌ You need Manage Server permission to set the welcome channel.")
return
# Save to database (implement this)
save_welcome_channel(ctx.guild.id, channel.id)
await ctx.send(f"✅ Welcome channel set to {channel.mention}")
@config.command()
async def autorole(ctx, role: nextcord.Role):
"""Set the auto role for new members."""
if not ctx.author.guild_permissions.manage_roles:
await ctx.send("❌ You need Manage Roles permission to set auto roles.")
return
# Check if bot can assign the role
if role.position >= ctx.me.top_role.position:
await ctx.send("❌ I cannot assign this role (it's higher than my highest role).")
return
# Save to database (implement this)
save_auto_role(ctx.guild.id, role.id)
await ctx.send(f"✅ Auto role set to {role.mention}")
# Nested groups
@bot.group()
async def admin(ctx):
"""Admin-only commands."""
pass
@admin.group()
async def user(ctx):
"""User management commands."""
pass
@user.command()
async def ban_user(ctx, member: nextcord.Member, *, reason: str = "No reason provided"):
"""Ban a user (admin command)."""
# Implementation here
await ctx.send(f"Banned {member.mention} for: {reason}")
@user.command()
async def unban(ctx, user_id: int):
"""Unban a user by ID."""
try:
user = await bot.fetch_user(user_id)
await ctx.guild.unban(user)
await ctx.send(f"✅ Unbanned {user}")
except nextcord.NotFound:
await ctx.send("❌ User not found or not banned.")
except nextcord.Forbidden:
await ctx.send("❌ I don't have permission to unban users.")Type conversion for command arguments with custom parsing and validation.
# Common built-in converters
class MemberConverter:
"""Converts argument to nextcord.Member."""
async def convert(self, ctx: Context, argument: str) -> nextcord.Member:
...
class UserConverter:
"""Converts argument to nextcord.User."""
async def convert(self, ctx: Context, argument: str) -> nextcord.User:
...
class TextChannelConverter:
"""Converts argument to nextcord.TextChannel."""
async def convert(self, ctx: Context, argument: str) -> nextcord.TextChannel:
...
class RoleConverter:
"""Converts argument to nextcord.Role."""
async def convert(self, ctx: Context, argument: str) -> nextcord.Role:
...
class ColourConverter:
"""Converts argument to nextcord.Colour."""
async def convert(self, ctx: Context, argument: str) -> nextcord.Colour:
...
# Using built-in converters
@bot.command()
async def give_role(ctx, member: nextcord.Member, role: nextcord.Role):
"""Give a role to a member."""
if role in member.roles:
await ctx.send(f"{member.mention} already has the {role.mention} role.")
return
try:
await member.add_roles(role)
await ctx.send(f"✅ Gave {role.mention} to {member.mention}")
except nextcord.Forbidden:
await ctx.send("❌ I don't have permission to assign roles.")
@bot.command()
async def color_role(ctx, role: nextcord.Role, color: nextcord.Colour):
"""Change a role's color."""
try:
await role.edit(color=color)
await ctx.send(f"✅ Changed {role.mention} color to {color}")
except nextcord.Forbidden:
await ctx.send("❌ I don't have permission to edit roles.")
# Custom converters
class DurationConverter(commands.Converter):
"""Convert duration strings like '1h30m' to seconds."""
async def convert(self, ctx: Context, argument: str) -> int:
import re
# Parse duration string (e.g., "1h30m15s")
time_regex = re.compile(r"(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?")
match = time_regex.match(argument.lower())
if not match or not any(match.groups()):
raise commands.BadArgument(f"Invalid duration format: {argument}")
hours, minutes, seconds = match.groups()
total_seconds = 0
if hours:
total_seconds += int(hours) * 3600
if minutes:
total_seconds += int(minutes) * 60
if seconds:
total_seconds += int(seconds)
if total_seconds <= 0:
raise commands.BadArgument("Duration must be greater than 0")
return total_seconds
class TemperatureConverter(commands.Converter):
"""Convert temperature between Celsius and Fahrenheit."""
async def convert(self, ctx: Context, argument: str) -> dict:
import re
# Match patterns like "25c", "77f", "25°C", "77°F"
pattern = r"^(-?\d+(?:\.\d+)?)([cf])$"
match = re.match(pattern, argument.lower().replace("°", ""))
if not match:
raise commands.BadArgument(f"Invalid temperature format: {argument}")
value, unit = match.groups()
value = float(value)
if unit == 'c':
celsius = value
fahrenheit = (value * 9/5) + 32
else: # fahrenheit
fahrenheit = value
celsius = (value - 32) * 5/9
return {
'celsius': round(celsius, 2),
'fahrenheit': round(fahrenheit, 2),
'original_unit': unit.upper()
}
# Using custom converters
@bot.command()
async def timeout(ctx, member: nextcord.Member, duration: DurationConverter):
"""Timeout a member for a specified duration."""
if not ctx.author.guild_permissions.moderate_members:
await ctx.send("❌ You don't have permission to timeout members.")
return
from datetime import datetime, timedelta
until = datetime.utcnow() + timedelta(seconds=duration)
try:
await member.timeout(until=until)
# Convert duration back to readable format
hours, remainder = divmod(duration, 3600)
minutes, seconds = divmod(remainder, 60)
duration_str = []
if hours:
duration_str.append(f"{hours}h")
if minutes:
duration_str.append(f"{minutes}m")
if seconds:
duration_str.append(f"{seconds}s")
await ctx.send(f"✅ Timed out {member.mention} for {' '.join(duration_str)}")
except nextcord.Forbidden:
await ctx.send("❌ I don't have permission to timeout this member.")
@bot.command()
async def temp(ctx, temperature: TemperatureConverter):
"""Convert temperature between Celsius and Fahrenheit."""
embed = nextcord.Embed(title="Temperature Conversion", color=nextcord.Color.blue())
embed.add_field(name="Celsius", value=f"{temperature['celsius']}°C", inline=True)
embed.add_field(name="Fahrenheit", value=f"{temperature['fahrenheit']}°F", inline=True)
embed.add_field(name="Original", value=f"Input was in {temperature['original_unit']}", inline=False)
await ctx.send(embed=embed)
# Union converters for multiple types
from typing import Union
@bot.command()
async def info(ctx, target: Union[nextcord.Member, nextcord.TextChannel, nextcord.Role]):
"""Get information about a member, channel, or role."""
embed = nextcord.Embed(color=nextcord.Color.blue())
if isinstance(target, nextcord.Member):
embed.title = f"Member: {target.display_name}"
embed.add_field(name="ID", value=target.id, inline=True)
embed.add_field(name="Joined", value=target.joined_at.strftime("%Y-%m-%d") if target.joined_at else "Unknown", inline=True)
embed.set_thumbnail(url=target.display_avatar.url)
elif isinstance(target, nextcord.TextChannel):
embed.title = f"Channel: #{target.name}"
embed.add_field(name="ID", value=target.id, inline=True)
embed.add_field(name="Category", value=target.category.name if target.category else "None", inline=True)
embed.add_field(name="Topic", value=target.topic or "No topic", inline=False)
elif isinstance(target, nextcord.Role):
embed.title = f"Role: @{target.name}"
embed.add_field(name="ID", value=target.id, inline=True)
embed.add_field(name="Members", value=len(target.members), inline=True)
embed.add_field(name="Position", value=target.position, inline=True)
embed.color = target.color
await ctx.send(embed=embed)Permission and condition checking decorators for command access control.
from nextcord.ext import commands
# Permission-based checks
@commands.has_permissions(manage_messages=True)
@bot.command()
async def purge(ctx, amount: int):
"""Delete messages (requires manage_messages permission)."""
if amount > 100:
await ctx.send("❌ Cannot delete more than 100 messages at once.")
return
deleted = await ctx.channel.purge(limit=amount + 1) # +1 for command message
await ctx.send(f"✅ Deleted {len(deleted) - 1} messages.", delete_after=5)
@commands.has_any_role("Admin", "Moderator")
@bot.command()
async def mod_command(ctx):
"""Command available to users with Admin or Moderator role."""
await ctx.send("This is a moderator command!")
@commands.has_role("VIP")
@bot.command()
async def vip_command(ctx):
"""Command available only to VIP members."""
await ctx.send("Welcome to the VIP area!")
# Context-based checks
@commands.guild_only()
@bot.command()
async def server_info(ctx):
"""Get server information (guild only)."""
guild = ctx.guild
embed = nextcord.Embed(title=guild.name, color=nextcord.Color.blue())
embed.set_thumbnail(url=guild.icon.url if guild.icon else None)
embed.add_field(name="Members", value=guild.member_count, inline=True)
embed.add_field(name="Channels", value=len(guild.channels), inline=True)
embed.add_field(name="Roles", value=len(guild.roles), inline=True)
embed.add_field(name="Owner", value=guild.owner.mention, inline=True)
embed.add_field(name="Created", value=guild.created_at.strftime("%Y-%m-%d"), inline=True)
await ctx.send(embed=embed)
@commands.dm_only()
@bot.command()
async def secret(ctx):
"""A command that only works in DMs."""
await ctx.send("🤫 This is a secret message that only works in DMs!")
@commands.is_owner()
@bot.command()
async def shutdown(ctx):
"""Shut down the bot (owner only)."""
await ctx.send("Shutting down...")
await bot.close()
# Cooldown checks
@commands.cooldown(1, 30.0, commands.BucketType.user)
@bot.command()
async def daily(ctx):
"""Claim daily reward (once per 30 seconds per user)."""
# This would typically involve a database
reward = 100
await ctx.send(f"🎁 You claimed your daily reward of {reward} coins!")
@commands.cooldown(2, 60.0, commands.BucketType.guild)
@bot.command()
async def server_command(ctx):
"""A command with server-wide cooldown (2 uses per minute per guild)."""
await ctx.send("This command has a server-wide cooldown!")
# Custom checks
def is_in_voice():
"""Check if user is in a voice channel."""
async def predicate(ctx):
return ctx.author.voice is not None
return commands.check(predicate)
def bot_has_permissions(**perms):
"""Check if bot has specific permissions."""
async def predicate(ctx):
bot_perms = ctx.channel.permissions_for(ctx.me)
missing = [perm for perm, value in perms.items() if getattr(bot_perms, perm) != value]
if missing:
raise commands.BotMissingPermissions(missing)
return True
return commands.check(predicate)
@is_in_voice()
@bot.command()
async def voice_info(ctx):
"""Get information about your current voice channel."""
voice = ctx.author.voice
channel = voice.channel
embed = nextcord.Embed(
title=f"Voice Channel: {channel.name}",
color=nextcord.Color.green()
)
embed.add_field(name="Members", value=len(channel.members), inline=True)
embed.add_field(name="Bitrate", value=f"{channel.bitrate // 1000}kbps", inline=True)
embed.add_field(name="User Limit", value=channel.user_limit or "No limit", inline=True)
if voice.self_mute:
embed.add_field(name="Status", value="🔇 Self-muted", inline=True)
if voice.self_deaf:
embed.add_field(name="Status", value="🔇 Self-deafened", inline=True)
await ctx.send(embed=embed)
@bot_has_permissions(embed_links=True, attach_files=True)
@bot.command()
async def fancy_command(ctx):
"""A command that requires bot to have embed and file permissions."""
embed = nextcord.Embed(title="Fancy Command", description="This requires special permissions!")
# Create a simple text file
import io
file_content = "This is a generated file!"
file = nextcord.File(io.BytesIO(file_content.encode()), filename="generated.txt")
await ctx.send(embed=embed, file=file)
# Global check for all commands
@bot.check
async def globally_block_dms(ctx):
"""Global check to block all commands in DMs."""
return ctx.guild is not None
# Per-command check override
@bot.command()
@commands.check(lambda ctx: True) # Override global check
async def help_dm(ctx):
"""Help command that works in DMs despite global check."""
await ctx.send("This help command works everywhere!")Comprehensive error handling for command execution with custom error responses.
@bot.event
async def on_command_error(ctx, error):
"""Global command error handler."""
# Ignore errors from commands that have local error handlers
if hasattr(ctx.command, 'on_error'):
return
# Ignore errors from cogs that have local error handlers
if ctx.cog and ctx.cog.has_error_handler():
return
# Handle specific error types
if isinstance(error, commands.CommandNotFound):
# Optionally ignore or suggest similar commands
return
elif isinstance(error, commands.MissingRequiredArgument):
embed = nextcord.Embed(
title="❌ Missing Argument",
description=f"Missing required argument: `{error.param.name}`",
color=nextcord.Color.red()
)
# Show command usage
if ctx.command.help:
embed.add_field(name="Usage", value=f"`{ctx.prefix}{ctx.command.qualified_name} {ctx.command.signature}`", inline=False)
embed.add_field(name="Help", value=ctx.command.help, inline=False)
await ctx.send(embed=embed)
elif isinstance(error, commands.BadArgument):
embed = nextcord.Embed(
title="❌ Invalid Argument",
description=str(error),
color=nextcord.Color.red()
)
await ctx.send(embed=embed)
elif isinstance(error, commands.MissingPermissions):
missing_perms = ", ".join(error.missing_permissions)
await ctx.send(f"❌ You're missing required permissions: {missing_perms}")
elif isinstance(error, commands.BotMissingPermissions):
missing_perms = ", ".join(error.missing_permissions)
await ctx.send(f"❌ I'm missing required permissions: {missing_perms}")
elif isinstance(error, commands.NoPrivateMessage):
await ctx.send("❌ This command cannot be used in private messages.")
elif isinstance(error, commands.PrivateMessageOnly):
await ctx.send("❌ This command can only be used in private messages.")
elif isinstance(error, commands.CheckFailure):
await ctx.send("❌ You don't have permission to use this command.")
elif isinstance(error, commands.CommandOnCooldown):
time_left = round(error.retry_after, 1)
await ctx.send(f"⏰ Command is on cooldown. Try again in {time_left} seconds.")
elif isinstance(error, commands.MaxConcurrencyReached):
await ctx.send(f"❌ Too many people are using this command. Try again later.")
elif isinstance(error, commands.CommandInvokeError):
# Handle errors that occur during command execution
original_error = error.original
if isinstance(original_error, nextcord.Forbidden):
await ctx.send("❌ I don't have permission to perform this action.")
elif isinstance(original_error, nextcord.NotFound):
await ctx.send("❌ The requested resource was not found.")
elif isinstance(original_error, nextcord.HTTPException):
await ctx.send("❌ An error occurred while communicating with Discord.")
else:
# Log unexpected errors
print(f"Unexpected error in {ctx.command}: {original_error}")
await ctx.send("❌ An unexpected error occurred. Please try again later.")
else:
# Log unhandled errors
print(f"Unhandled error type: {type(error).__name__}: {error}")
await ctx.send("❌ An error occurred while processing the command.")
# Command-specific error handler
@bot.command()
async def divide(ctx, a: float, b: float):
"""Divide two numbers."""
try:
result = a / b
await ctx.send(f"{a} ÷ {b} = {result}")
except ZeroDivisionError:
await ctx.send("❌ Cannot divide by zero!")
@divide.error
async def divide_error(ctx, error):
"""Local error handler for the divide command."""
if isinstance(error, commands.BadArgument):
await ctx.send("❌ Please provide valid numbers for division.")
# Custom exceptions
class CustomCommandError(commands.CommandError):
"""Base class for custom command errors."""
pass
class InsufficientFundsError(CustomCommandError):
"""Raised when user doesn't have enough money."""
def __init__(self, required: int, available: int):
self.required = required
self.available = available
super().__init__(f"Insufficient funds: need {required}, have {available}")
@bot.command()
async def buy(ctx, item: str, amount: int):
"""Buy an item from the shop."""
# Mock data
user_balance = 50
item_price = 25
total_cost = item_price * amount
if user_balance < total_cost:
raise InsufficientFundsError(total_cost, user_balance)
# Process purchase
await ctx.send(f"✅ Purchased {amount}x {item} for {total_cost} coins!")
@buy.error
async def buy_error(ctx, error):
"""Error handler for buy command."""
if isinstance(error, InsufficientFundsError):
embed = nextcord.Embed(
title="💰 Insufficient Funds",
description=f"You need {error.required} coins but only have {error.available}.",
color=nextcord.Color.red()
)
embed.add_field(name="Shortfall", value=f"{error.required - error.available} coins", inline=True)
await ctx.send(embed=embed)Advanced command framework features including dynamic command loading and help systems.
class CustomHelpCommand(commands.DefaultHelpCommand):
"""Custom help command with embeds and better formatting."""
def __init__(self):
super().__init__(
command_attrs={
'name': 'help',
'aliases': ['h'],
'help': 'Shows help about the bot, a command, or a category'
}
)
async def send_bot_help(self, mapping):
"""Send help for the entire bot."""
embed = nextcord.Embed(
title="Bot Help",
description="Here are all available commands:",
color=nextcord.Color.blue()
)
for cog, commands in mapping.items():
filtered_commands = await self.filter_commands(commands, sort=True)
if not filtered_commands:
continue
cog_name = getattr(cog, 'qualified_name', 'No Category')
command_list = [f"`{c.name}`" for c in filtered_commands]
embed.add_field(
name=cog_name,
value=" • ".join(command_list) or "No commands",
inline=False
)
embed.set_footer(text=f"Use {self.context.prefix}help <command> for more info on a command.")
channel = self.get_destination()
await channel.send(embed=embed)
async def send_command_help(self, command):
"""Send help for a specific command."""
embed = nextcord.Embed(
title=f"Command: {command.qualified_name}",
description=command.help or "No description available",
color=nextcord.Color.blue()
)
embed.add_field(
name="Usage",
value=f"`{self.context.prefix}{command.qualified_name} {command.signature}`",
inline=False
)
if command.aliases:
embed.add_field(
name="Aliases",
value=", ".join(f"`{alias}`" for alias in command.aliases),
inline=False
)
if isinstance(command, commands.Group):
subcommands = [f"`{c.name}`" for c in command.commands]
if subcommands:
embed.add_field(
name="Subcommands",
value=" • ".join(subcommands),
inline=False
)
channel = self.get_destination()
await channel.send(embed=embed)
async def send_group_help(self, group):
"""Send help for a command group."""
embed = nextcord.Embed(
title=f"Command Group: {group.qualified_name}",
description=group.help or "No description available",
color=nextcord.Color.blue()
)
embed.add_field(
name="Usage",
value=f"`{self.context.prefix}{group.qualified_name} {group.signature}`",
inline=False
)
filtered_commands = await self.filter_commands(group.commands, sort=True)
if filtered_commands:
command_list = []
for command in filtered_commands:
command_list.append(f"`{command.name}` - {command.short_doc or 'No description'}")
embed.add_field(
name="Subcommands",
value="\n".join(command_list),
inline=False
)
channel = self.get_destination()
await channel.send(embed=embed)
# Set custom help command
bot.help_command = CustomHelpCommand()
# Pagination for long help outputs
class PaginatedHelpCommand(commands.HelpCommand):
"""Help command with pagination for large outputs."""
def __init__(self):
super().__init__(
command_attrs={
'name': 'help',
'help': 'Shows help with pagination'
}
)
async def send_bot_help(self, mapping):
"""Send paginated bot help."""
pages = []
for cog, commands in mapping.items():
filtered_commands = await self.filter_commands(commands, sort=True)
if not filtered_commands:
continue
embed = nextcord.Embed(
title=f"Commands: {getattr(cog, 'qualified_name', 'No Category')}",
color=nextcord.Color.blue()
)
for command in filtered_commands:
embed.add_field(
name=f"{self.context.prefix}{command.qualified_name}",
value=command.short_doc or "No description",
inline=False
)
pages.append(embed)
if not pages:
await self.get_destination().send("No commands available.")
return
# Simple pagination (you could use a more sophisticated system)
for i, page in enumerate(pages):
page.set_footer(text=f"Page {i+1}/{len(pages)}")
await self.get_destination().send(embed=page)
# Dynamic command loading
async def load_extension_command(ctx, extension: str):
"""Dynamically load a bot extension."""
try:
bot.load_extension(f"cogs.{extension}")
await ctx.send(f"✅ Loaded extension: {extension}")
except commands.ExtensionError as e:
await ctx.send(f"❌ Failed to load extension: {e}")
async def unload_extension_command(ctx, extension: str):
"""Dynamically unload a bot extension."""
try:
bot.unload_extension(f"cogs.{extension}")
await ctx.send(f"✅ Unloaded extension: {extension}")
except commands.ExtensionError as e:
await ctx.send(f"❌ Failed to unload extension: {e}")
async def reload_extension_command(ctx, extension: str):
"""Dynamically reload a bot extension."""
try:
bot.reload_extension(f"cogs.{extension}")
await ctx.send(f"✅ Reloaded extension: {extension}")
except commands.ExtensionError as e:
await ctx.send(f"❌ Failed to reload extension: {e}")This comprehensive documentation covers all major aspects of nextcord's traditional command framework, providing developers with the tools needed to create sophisticated text-based bot interfaces alongside modern application commands.
Install with Tessl CLI
npx tessl i tessl/pypi-nextcord