CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-nextcord

A Python wrapper for the Discord API forked from discord.py

Pending
Overview
Eval results
Files

commands.mddocs/

Nextcord Command Framework

Traditional text-based command system with the commands extension, providing prefix commands, command groups, converters, and checks for comprehensive bot functionality.

Bot Class

Enhanced client with command processing capabilities and prefix-based command handling.

Bot Constructor and Setup { .api }

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)

Commands

Individual command definitions with parameters, converters, and error handling.

Command Decorator { .api }

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)

Command Context { .api }

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.")

Command Groups

Hierarchical command organization with subcommands and groups.

Group Commands { .api }

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.")

Converters

Type conversion for command arguments with custom parsing and validation.

Built-in Converters { .api }

# 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)

Checks

Permission and condition checking decorators for command access control.

Built-in Checks { .api }

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!")

Error Handling

Comprehensive error handling for command execution with custom error responses.

Error Handlers { .api }

@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 Features

Advanced command framework features including dynamic command loading and help systems.

Custom Help Command { .api }

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

docs

application-commands.md

channels.md

client.md

commands.md

errors.md

events.md

guild.md

index.md

messages.md

permissions.md

tasks.md

ui.md

users.md

utilities.md

voice.md

webhooks.md

tile.json