A modern, easy-to-use, feature-rich async-ready API wrapper for Discord written in Python
—
Traditional message-based command framework with comprehensive command handling, argument parsing, permission checks, cooldowns, error handling, and organized command structure through cogs for text-based bot functionality.
Command-enabled bot classes extending basic client functionality with message command processing.
class Bot(commands.BotBase, Client):
def __init__(
self,
command_prefix: Union[str, Iterable[str], Callable[[Bot, Message], Union[str, Iterable[str]]]],
*,
help_command: Optional[commands.HelpCommand] = ...,
description: Optional[str] = None,
**options
):
"""
Initialize a message command bot.
Parameters:
- command_prefix: Command prefix(es) or callable returning prefix(es)
- help_command: Custom help command implementation
- description: Bot description for help command
- options: Additional client options
"""
@property
def commands(self) -> Set[commands.Command]:
"""All registered commands."""
@property
def cogs(self) -> Mapping[str, commands.Cog]:
"""All loaded cogs."""
def add_command(self, command: commands.Command) -> None:
"""
Add a command to the bot.
Parameters:
- command: Command to add
"""
def remove_command(self, name: str) -> Optional[commands.Command]:
"""
Remove a command by name.
Parameters:
- name: Command name
Returns:
Removed command if found
"""
def get_command(self, name: str) -> Optional[commands.Command]:
"""
Get a command by name.
Parameters:
- name: Command name (supports subcommands with spaces)
Returns:
Command if found
"""
def walk_commands(self) -> Generator[commands.Command, None, None]:
"""
Iterate over all commands including subcommands.
Yields:
All registered commands
"""
async def get_prefix(self, message: Message) -> Union[List[str], str]:
"""
Get command prefix for a message.
Parameters:
- message: Message to get prefix for
Returns:
Prefix or list of prefixes
"""
async def get_context(self, message: Message, *, cls: Type[Context] = None) -> Context:
"""
Get command context from a message.
Parameters:
- message: Message to create context from
- cls: Custom context class
Returns:
Command context
"""
async def process_commands(self, message: Message) -> None:
"""
Process a message for commands.
Parameters:
- message: Message to process
"""
async def invoke(self, ctx: commands.Context) -> None:
"""
Invoke a command from context.
Parameters:
- ctx: Command context
"""
def command(self, name: str = None, **kwargs):
"""
Decorator for registering commands.
Parameters:
- name: Command name (optional)
- kwargs: Command options
"""
def group(self, name: str = None, **kwargs):
"""
Decorator for registering command groups.
Parameters:
- name: Group name (optional)
- kwargs: Group options
"""
def listen(self, name: str = None):
"""
Decorator for event listeners.
Parameters:
- name: Event name (optional)
"""
def add_cog(self, cog: commands.Cog, *, override: bool = False) -> None:
"""
Add a cog to the bot.
Parameters:
- cog: Cog instance to add
- override: Whether to override existing commands
"""
def remove_cog(self, name: str) -> Optional[commands.Cog]:
"""
Remove a cog by name.
Parameters:
- name: Cog name
Returns:
Removed cog if found
"""
def get_cog(self, name: str) -> Optional[commands.Cog]:
"""
Get a cog by name.
Parameters:
- name: Cog name
Returns:
Cog if found
"""
async def load_extension(self, name: str, *, package: Optional[str] = None) -> None:
"""
Load an extension module.
Parameters:
- name: Extension module name
- package: Package name for relative imports
"""
async def unload_extension(self, name: str, *, package: Optional[str] = None) -> None:
"""
Unload an extension module.
Parameters:
- name: Extension module name
- package: Package name for relative imports
"""
async def reload_extension(self, name: str, *, package: Optional[str] = None) -> None:
"""
Reload an extension module.
Parameters:
- name: Extension module name
- package: Package name for relative imports
"""
class AutoShardedBot(commands.AutoShardedBotBase, Bot):
"""Auto-sharded message command bot."""
def __init__(self, command_prefix, **options):
"""Initialize auto-sharded command bot."""Command representation and execution with parameter handling and validation.
class Command:
def __init__(
self,
func: Callable,
*,
name: str = None,
aliases: List[str] = None,
help: str = None,
brief: str = None,
usage: str = None,
description: str = None,
enabled: bool = True,
hidden: bool = False,
ignore_extra: bool = True,
cooldown_after_parsing: bool = False,
extras: Dict[str, Any] = None,
**kwargs
):
"""
Initialize a command.
Parameters:
- func: Command callback function
- name: Command name
- aliases: Command aliases
- help: Help text
- brief: Brief description
- usage: Usage syntax
- description: Long description
- enabled: Whether command is enabled
- hidden: Whether to hide from help
- ignore_extra: Ignore extra arguments
- cooldown_after_parsing: Apply cooldown after parsing
- extras: Extra metadata
"""
name: str
callback: Callable
help: Optional[str]
brief: Optional[str]
usage: Optional[str]
aliases: List[str]
enabled: bool
hidden: bool
checks: List[Check]
description: Optional[str]
signature: str
extras: Dict[str, Any]
@property
def cog(self) -> Optional[Cog]:
"""Cog the command belongs to."""
@property
def short_doc(self) -> Optional[str]:
"""Short documentation string."""
@property
def signature(self) -> str:
"""Command signature with parameters."""
def can_run(self, ctx: Context) -> bool:
"""
Check if command can be run in context.
Parameters:
- ctx: Command context
Returns:
True if command can run
"""
async def invoke(self, ctx: Context) -> None:
"""
Invoke the command.
Parameters:
- ctx: Command context
"""
async def reinvoke(self, ctx: Context, *, call_hooks: bool = False) -> None:
"""
Reinvoke the command bypassing cooldowns.
Parameters:
- ctx: Command context
- call_hooks: Whether to call before/after hooks
"""
def error(self, coro):
"""
Decorator for command error handlers.
Parameters:
- coro: Error handler coroutine
"""
def before_invoke(self, coro):
"""
Decorator for pre-command hooks.
Parameters:
- coro: Hook coroutine
"""
def after_invoke(self, coro):
"""
Decorator for post-command hooks.
Parameters:
- coro: Hook coroutine
"""
class Group(Command):
"""Command group container for subcommands."""
def __init__(
self,
*args,
invoke_without_subcommand: bool = False,
case_insensitive: bool = False,
**kwargs
):
"""
Initialize a command group.
Parameters:
- invoke_without_subcommand: Allow invoking group without subcommand
- case_insensitive: Case insensitive subcommand matching
"""
@property
def commands(self) -> Set[Command]:
"""Subcommands in this group."""
def add_command(self, command: Command) -> None:
"""
Add a subcommand.
Parameters:
- command: Subcommand to add
"""
def remove_command(self, name: str) -> Optional[Command]:
"""
Remove a subcommand.
Parameters:
- name: Subcommand name
Returns:
Removed command if found
"""
def get_command(self, name: str) -> Optional[Command]:
"""
Get a subcommand.
Parameters:
- name: Subcommand name
Returns:
Command if found
"""
def walk_commands(self) -> Generator[Command, None, None]:
"""
Walk all commands in group recursively.
Yields:
All commands and subcommands
"""
def command(self, *args, **kwargs):
"""Decorator for adding subcommands."""
def group(self, *args, **kwargs):
"""Decorator for adding subgroups."""Command invocation context providing access to message, channel, guild, and bot state.
class Context:
def __init__(
self,
*,
message: Message,
bot: Bot,
prefix: str = None,
command: Command = None,
invoked_with: str = None,
invoked_parents: List[str] = None,
invoked_subcommand: Command = None,
subcommand_passed: str = None,
command_failed: bool = False,
current_parameter: Parameter = None
):
"""
Initialize command context.
Parameters:
- message: Message that triggered command
- bot: Bot instance
- prefix: Used command prefix
- command: Invoked command
- invoked_with: Command name used for invocation
- invoked_parents: Parent command names
- invoked_subcommand: Invoked subcommand
- subcommand_passed: Subcommand argument
- command_failed: Whether command execution failed
- current_parameter: Currently parsing parameter
"""
message: Message
bot: Bot
command: Optional[Command]
invoked_with: Optional[str]
invoked_parents: List[str]
invoked_subcommand: Optional[Command]
subcommand_passed: Optional[str]
command_failed: bool
prefix: Optional[str]
args: List[Any]
kwargs: Dict[str, Any]
@property
def author(self) -> Union[User, Member]:
"""Message author."""
@property
def guild(self) -> Optional[Guild]:
"""Guild where command was invoked."""
@property
def channel(self) -> Messageable:
"""Channel where command was invoked."""
@property
def me(self) -> Union[Member, ClientUser]:
"""Bot's member object in guild."""
@property
def voice_client(self) -> Optional[VoiceProtocol]:
"""Voice client for the guild."""
@property
def valid(self) -> bool:
"""Whether context is valid for command invocation."""
@property
def clean_prefix(self) -> str:
"""Cleaned command prefix."""
@property
def cog(self) -> Optional[Cog]:
"""Cog of the invoked command."""
async def invoke(
self,
command: Command,
*args,
**kwargs
) -> None:
"""
Invoke another command.
Parameters:
- command: Command to invoke
- args: Command arguments
- kwargs: Command keyword arguments
"""
async def reinvoke(self, *, call_hooks: bool = False, restart: bool = True) -> None:
"""
Reinvoke the current command.
Parameters:
- call_hooks: Whether to call before/after hooks
- restart: Whether to restart from beginning
"""
async def send(
self,
content: str = None,
*,
tts: bool = False,
embed: Embed = None,
embeds: List[Embed] = None,
file: File = None,
files: List[File] = None,
allowed_mentions: AllowedMentions = None,
reference: Union[Message, MessageReference, PartialMessage] = None,
mention_author: bool = None,
view: View = None,
components: Union[ActionRow, List[ActionRow]] = None,
delete_after: float = None,
suppress_embeds: bool = False,
flags: MessageFlags = None,
ephemeral: bool = False
) -> Message:
"""
Send a message to the context channel.
Parameters:
- content: Message content
- tts: Text-to-speech
- embed: Single embed
- embeds: List of embeds
- file: Single file
- files: List of files
- allowed_mentions: Mention configuration
- reference: Message to reply to
- mention_author: Whether to mention author in reply
- view: UI components view
- components: Raw components
- delete_after: Auto-delete after seconds
- suppress_embeds: Suppress embeds
- flags: Message flags
- ephemeral: Send as ephemeral (slash command contexts only)
Returns:
Sent message
"""
async def reply(self, content: str = None, **kwargs) -> Message:
"""
Reply to the context message.
Parameters:
- content: Reply content
- kwargs: Additional send parameters
Returns:
Sent reply message
"""
def typing(self) -> Typing:
"""
Context manager for typing indicator.
Returns:
Typing context manager
"""
def history(self, **kwargs) -> AsyncIterator[Message]:
"""
Get channel message history.
Parameters:
- kwargs: History parameters
Yields:
Messages from channel history
"""
async def fetch_message(self, id: int) -> Message:
"""
Fetch a message by ID from context channel.
Parameters:
- id: Message ID
Returns:
Message object
"""
def get_parameter(self, name: str) -> Optional[Parameter]:
"""
Get command parameter by name.
Parameters:
- name: Parameter name
Returns:
Parameter if found
"""
class GuildContext(Context):
"""Guild-specific context with additional guild properties."""
author: Member
guild: Guild
me: Member
@property
def permissions(self) -> Permissions:
"""Author's permissions in channel."""
@property
def bot_permissions(self) -> Permissions:
"""Bot's permissions in channel."""Modular command organization system for grouping related functionality.
class Cog:
"""Base class for command cogs."""
def __init_subclass__(cls, **kwargs):
"""Initialize cog subclass."""
@classmethod
def listener(cls, name: str = None):
"""
Decorator for event listeners in cogs.
Parameters:
- name: Event name (optional)
"""
def get_commands(self) -> List[Command]:
"""
Get all commands in this cog.
Returns:
List of cog commands
"""
def walk_commands(self) -> Generator[Command, None, None]:
"""
Walk all commands in cog.
Yields:
All cog commands
"""
def get_listeners(self) -> List[Tuple[str, Callable]]:
"""
Get all event listeners in this cog.
Returns:
List of (event_name, callback) tuples
"""
@property
def qualified_name(self) -> str:
"""Qualified cog name."""
@property
def description(self) -> Optional[str]:
"""Cog description."""
def cog_load(self) -> None:
"""Called when cog is loaded."""
def cog_unload(self) -> None:
"""Called when cog is unloaded."""
async def cog_check(self, ctx: Context) -> bool:
"""
Global check for all commands in cog.
Parameters:
- ctx: Command context
Returns:
True if commands can run
"""
async def cog_command_error(self, ctx: Context, error: Exception) -> None:
"""
Error handler for cog commands.
Parameters:
- ctx: Command context
- error: Exception raised
"""
async def cog_before_invoke(self, ctx: Context) -> None:
"""
Called before any cog command is invoked.
Parameters:
- ctx: Command context
"""
async def cog_after_invoke(self, ctx: Context) -> None:
"""
Called after any cog command is invoked.
Parameters:
- ctx: Command context
"""
def command(name: str = None, **attrs):
"""
Decorator for creating commands.
Parameters:
- name: Command name
- attrs: Command attributes
"""
def group(name: str = None, **attrs):
"""
Decorator for creating command groups.
Parameters:
- name: Group name
- attrs: Group attributes
"""
async def setup(bot: Bot):
"""
Setup function for extension modules.
Parameters:
- bot: Bot instance
"""Advanced parameter parsing, type conversion, and validation for command arguments.
class Parameter:
"""Command parameter information."""
def __init__(
self,
name: str,
kind: inspect.Parameter.kind,
*,
default: Any = inspect.Parameter.empty,
annotation: Any = inspect.Parameter.empty,
converter: Converter = None,
description: str = None,
displayed_default: str = None
):
"""
Initialize parameter.
Parameters:
- name: Parameter name
- kind: Parameter kind
- default: Default value
- annotation: Type annotation
- converter: Type converter
- description: Parameter description
- displayed_default: Default value display
"""
name: str
default: Any
annotation: Any
converter: Converter
kind: inspect.Parameter.kind
description: Optional[str]
displayed_default: Optional[str]
class Greedy:
"""Greedy converter for consuming multiple arguments."""
def __init__(self, converter: Converter):
"""
Initialize greedy converter.
Parameters:
- converter: Converter for individual arguments
"""
def param(
default: Any = ...,
*,
converter: Converter = None,
description: str = None,
displayed_default: str = None,
name: str = None
) -> Any:
"""
Configure command parameter.
Parameters:
- default: Default value
- converter: Type converter
- description: Parameter description
- displayed_default: Default display value
- name: Parameter name override
Returns:
Configured parameter
"""
# Built-in converters
class MemberConverter:
"""Convert argument to Member."""
async def convert(self, ctx: Context, argument: str) -> Member:
"""Convert string to Member."""
class UserConverter:
"""Convert argument to User."""
async def convert(self, ctx: Context, argument: str) -> User:
"""Convert string to User."""
class GuildConverter:
"""Convert argument to Guild."""
async def convert(self, ctx: Context, argument: str) -> Guild:
"""Convert string to Guild."""
class RoleConverter:
"""Convert argument to Role."""
async def convert(self, ctx: Context, argument: str) -> Role:
"""Convert string to Role."""
class TextChannelConverter:
"""Convert argument to TextChannel."""
async def convert(self, ctx: Context, argument: str) -> TextChannel:
"""Convert string to TextChannel."""
class VoiceChannelConverter:
"""Convert argument to VoiceChannel."""
async def convert(self, ctx: Context, argument: str) -> VoiceChannel:
"""Convert string to VoiceChannel."""
class CategoryChannelConverter:
"""Convert argument to CategoryChannel."""
async def convert(self, ctx: Context, argument: str) -> CategoryChannel:
"""Convert string to CategoryChannel."""
class EmojiConverter:
"""Convert argument to Emoji."""
async def convert(self, ctx: Context, argument: str) -> Emoji:
"""Convert string to Emoji."""
class PartialEmojiConverter:
"""Convert argument to PartialEmoji."""
async def convert(self, ctx: Context, argument: str) -> PartialEmoji:
"""Convert string to PartialEmoji."""
class ColourConverter:
"""Convert argument to Colour."""
async def convert(self, ctx: Context, argument: str) -> Colour:
"""Convert string to Colour."""
class MessageConverter:
"""Convert argument to Message."""
async def convert(self, ctx: Context, argument: str) -> Message:
"""Convert string to Message."""
class GameConverter:
"""Convert argument to Game activity."""
async def convert(self, ctx: Context, argument: str) -> Game:
"""Convert string to Game."""
class InviteConverter:
"""Convert argument to Invite."""
async def convert(self, ctx: Context, argument: str) -> Invite:
"""Convert string to Invite."""
class GuildStickerConverter:
"""Convert argument to GuildSticker."""
async def convert(self, ctx: Context, argument: str) -> GuildSticker:
"""Convert string to GuildSticker."""
class ScheduledEventConverter:
"""Convert argument to GuildScheduledEvent."""
async def convert(self, ctx: Context, argument: str) -> GuildScheduledEvent:
"""Convert string to GuildScheduledEvent."""
class ThreadConverter:
"""Convert argument to Thread."""
async def convert(self, ctx: Context, argument: str) -> Thread:
"""Convert string to Thread."""
# Union converters
typing.Union[Member, User] # Tries Member first, then User
typing.Optional[Member] # Optional Member (can be None)
typing.Literal['option1', 'option2'] # Literal choicesPermission checking system for command access control and security.
def check(predicate: Callable[[Context], bool]):
"""
Decorator for custom permission checks.
Parameters:
- predicate: Check function
"""
def check_any(*checks: Check):
"""
Decorator requiring any of the provided checks to pass.
Parameters:
- checks: Check decorators
"""
def has_role(name: Union[str, int]):
"""
Check if user has specific role.
Parameters:
- name: Role name or ID
"""
def has_any_role(*names: Union[str, int]):
"""
Check if user has any of the specified roles.
Parameters:
- names: Role names or IDs
"""
def has_permissions(**perms: bool):
"""
Check if user has specific permissions.
Parameters:
- perms: Permission flags
"""
def has_guild_permissions(**perms: bool):
"""
Check if user has guild permissions.
Parameters:
- perms: Permission flags
"""
def bot_has_permissions(**perms: bool):
"""
Check if bot has specific permissions.
Parameters:
- perms: Permission flags
"""
def bot_has_guild_permissions(**perms: bool):
"""
Check if bot has guild permissions.
Parameters:
- perms: Permission flags
"""
def is_owner():
"""Check if user is bot owner."""
def is_nsfw():
"""Check if channel is NSFW."""
def guild_only():
"""Check if command is in a guild."""
def dm_only():
"""Check if command is in DMs."""
def cooldown(rate: int, per: float, type: BucketType = BucketType.default):
"""
Apply cooldown to command.
Parameters:
- rate: Number of uses allowed
- per: Time period in seconds
- type: Cooldown bucket type
"""
def dynamic_cooldown(cooldown: Callable[[Context], Optional[Cooldown]], type: BucketType = BucketType.default):
"""
Apply dynamic cooldown to command.
Parameters:
- cooldown: Function returning cooldown configuration
- type: Cooldown bucket type
"""
def max_concurrency(number: int, *, per: BucketType = BucketType.default, wait: bool = False):
"""
Limit concurrent command usage.
Parameters:
- number: Maximum concurrent uses
- per: Concurrency bucket type
- wait: Whether to wait for slot
"""
class BucketType(enum.Enum):
"""Cooldown bucket types."""
default = 0
user = 1
guild = 2
channel = 3
member = 4
category = 5
role = 6Configurable help command system for documenting bot commands and usage.
class HelpCommand:
"""Base class for help commands."""
def __init__(
self,
*,
verify_checks: bool = True,
command_attrs: Dict[str, Any] = None,
sort_commands: bool = True,
show_hidden: bool = False,
dm_help: Optional[bool] = None,
dm_help_threshold: Optional[int] = 1000
):
"""
Initialize help command.
Parameters:
- verify_checks: Check command permissions
- command_attrs: Help command attributes
- sort_commands: Sort commands alphabetically
- show_hidden: Show hidden commands
- dm_help: Send help in DMs
- dm_help_threshold: Character threshold for DMing
"""
context: Optional[Context]
verify_checks: bool
command_attrs: Dict[str, Any]
sort_commands: bool
show_hidden: bool
dm_help: Optional[bool]
dm_help_threshold: Optional[int]
def add_indented_commands(
self,
commands: List[Command],
*,
heading: str,
max_size: Optional[int] = None
) -> None:
"""
Add indented command list to paginator.
Parameters:
- commands: Commands to add
- heading: Section heading
- max_size: Maximum name length
"""
def get_opening_note(self) -> str:
"""
Get help opening note.
Returns:
Opening note text
"""
def get_ending_note(self) -> str:
"""
Get help ending note.
Returns:
Ending note text
"""
def add_bot_commands_formatting(self, commands: List[Command], heading: str) -> None:
"""
Add bot commands to help.
Parameters:
- commands: Commands to format
- heading: Section heading
"""
def add_subcommand_formatting(self, command: Command) -> None:
"""
Add subcommand information.
Parameters:
- command: Subcommand to format
"""
def add_aliases_formatting(self, aliases: List[str]) -> None:
"""
Add command aliases.
Parameters:
- aliases: Command aliases
"""
def add_command_formatting(self, command: Command) -> None:
"""
Add single command information.
Parameters:
- command: Command to format
"""
def get_destination(self) -> Messageable:
"""
Get help destination channel.
Returns:
Destination for help message
"""
async def filter_commands(
self,
commands: Iterable[Command],
*,
sort: bool = False,
key: Optional[Callable[[Command], Any]] = None
) -> List[Command]:
"""
Filter commands based on checks.
Parameters:
- commands: Commands to filter
- sort: Whether to sort commands
- key: Sort key function
Returns:
Filtered command list
"""
def get_max_size(self, commands: List[Command]) -> int:
"""
Get maximum command name length.
Parameters:
- commands: Commands to check
Returns:
Maximum name length
"""
def get_command_signature(self, command: Command) -> str:
"""
Get command signature.
Parameters:
- command: Command to get signature for
Returns:
Command signature string
"""
async def send_bot_help(self, mapping: Dict[Optional[Cog], List[Command]]) -> None:
"""
Send bot-wide help.
Parameters:
- mapping: Cog to commands mapping
"""
async def send_cog_help(self, cog: Cog) -> None:
"""
Send cog-specific help.
Parameters:
- cog: Cog to get help for
"""
async def send_group_help(self, group: Group) -> None:
"""
Send group command help.
Parameters:
- group: Command group
"""
async def send_command_help(self, command: Command) -> None:
"""
Send single command help.
Parameters:
- command: Command to get help for
"""
async def prepare_help_command(self, ctx: Context, command: Optional[str] = None) -> None:
"""
Prepare help command for execution.
Parameters:
- ctx: Command context
- command: Specific command help requested
"""
async def command_not_found(self, string: str) -> str:
"""
Handle command not found.
Parameters:
- string: Command name that wasn't found
Returns:
Error message
"""
async def subcommand_not_found(self, command: Command, string: str) -> str:
"""
Handle subcommand not found.
Parameters:
- command: Parent command
- string: Subcommand name that wasn't found
Returns:
Error message
"""
async def send_error_message(self, error: str) -> None:
"""
Send error message.
Parameters:
- error: Error message to send
"""
async def on_help_command_error(self, ctx: Context, error: CommandError) -> None:
"""
Handle help command errors.
Parameters:
- ctx: Command context
- error: Error that occurred
"""
class DefaultHelpCommand(HelpCommand):
"""Default help command implementation."""
def __init__(
self,
*,
paginator: Optional[Paginator] = None,
**options
):
"""
Initialize default help command.
Parameters:
- paginator: Custom paginator
- options: Base class options
"""
class MinimalHelpCommand(HelpCommand):
"""Minimal help command implementation."""
def __init__(self, **options):
"""Initialize minimal help command."""
class Paginator:
"""Text paginator for long help content."""
def __init__(
self,
prefix: str = '```',
suffix: str = '```',
max_size: int = 2000,
linesep: str = '\n'
):
"""
Initialize paginator.
Parameters:
- prefix: Page prefix
- suffix: Page suffix
- max_size: Maximum page size
- linesep: Line separator
"""
@property
def pages(self) -> List[str]:
"""Paginated pages."""
def add_line(self, line: str = '', *, empty: bool = False) -> None:
"""
Add line to paginator.
Parameters:
- line: Line content
- empty: Whether to add empty line
"""
def close_page(self) -> None:
"""Close current page."""import disnake
from disnake.ext import commands
# Create bot with command prefix
bot = commands.Bot(
command_prefix='!',
description='A helpful bot',
intents=disnake.Intents.all(),
help_command=commands.DefaultHelpCommand(),
case_insensitive=True
)
@bot.event
async def on_ready():
print(f'Bot ready: {bot.user}')
@bot.event
async def on_command_error(ctx, error):
"""Global command error handler."""
if isinstance(error, commands.CommandNotFound):
return # Ignore unknown commands
elif isinstance(error, commands.MissingRequiredArgument):
await ctx.send(f"Missing argument: `{error.param}`")
elif isinstance(error, commands.BadArgument):
await ctx.send(f"Invalid argument: {error}")
elif isinstance(error, commands.CheckFailure):
await ctx.send("You don't have permission to use this command.")
elif isinstance(error, commands.CommandOnCooldown):
await ctx.send(f"Command on cooldown. Try again in {error.retry_after:.2f} seconds.")
else:
print(f'Command error: {error}')
await ctx.send("An error occurred while processing the command.")
# Basic commands
@bot.command()
async def ping(ctx):
"""Check bot latency."""
latency = round(bot.latency * 1000)
await ctx.send(f'Pong! Latency: {latency}ms')
@bot.command()
async def echo(ctx, *, message):
"""Echo a message."""
await ctx.send(message)
@bot.command()
async def userinfo(ctx, user: disnake.User = None):
"""Get information about a user."""
if user is None:
user = ctx.author
embed = disnake.Embed(title=f"User Info: {user}", color=0x00ff00)
embed.set_thumbnail(url=user.display_avatar.url)
embed.add_field(name="ID", value=user.id, inline=True)
embed.add_field(name="Created", value=f"<t:{int(user.created_at.timestamp())}:F>", inline=True)
if isinstance(user, disnake.Member):
embed.add_field(name="Joined", value=f"<t:{int(user.joined_at.timestamp())}:F>", inline=True)
embed.add_field(name="Roles", value=len(user.roles) - 1, inline=True)
await ctx.send(embed=embed)
bot.run('YOUR_BOT_TOKEN')@bot.group(invoke_without_subcommand=True, case_insensitive=True)
async def config(ctx):
"""Configuration commands."""
if ctx.invoked_subcommand is None:
await ctx.send("Available config options: prefix, welcome, autorole")
@config.command()
@commands.has_permissions(administrator=True)
async def prefix(ctx, new_prefix: str = None):
"""View or change bot prefix."""
if new_prefix is None:
current_prefix = await bot.get_prefix(ctx.message)
await ctx.send(f"Current prefix: `{current_prefix[0]}`")
else:
# In a real bot, save to database
bot.command_prefix = new_prefix
await ctx.send(f"Prefix changed to: `{new_prefix}`")
@config.command()
@commands.has_permissions(manage_guild=True)
async def welcome(ctx, channel: disnake.TextChannel = None):
"""Set welcome channel."""
if channel is None:
await ctx.send("Please specify a channel.")
return
# Save to database in real implementation
await ctx.send(f"Welcome channel set to {channel.mention}")
@bot.group()
@commands.has_permissions(manage_roles=True)
async def role(ctx):
"""Role management commands."""
if ctx.invoked_subcommand is None:
await ctx.send_help(ctx.command)
@role.command()
async def create(ctx, *, name: str):
"""Create a new role."""
role = await ctx.guild.create_role(name=name, reason=f"Created by {ctx.author}")
await ctx.send(f"Created role: {role.mention}")
@role.command()
async def delete(ctx, role: disnake.Role):
"""Delete a role."""
if role >= ctx.author.top_role:
return await ctx.send("You cannot delete this role.")
await role.delete(reason=f"Deleted by {ctx.author}")
await ctx.send(f"Deleted role: {role.name}")
@role.command()
async def give(ctx, member: disnake.Member, role: disnake.Role):
"""Give a role to a member."""
if role >= ctx.author.top_role:
return await ctx.send("You cannot assign this role.")
if role in member.roles:
return await ctx.send(f"{member} already has the {role.name} role.")
await member.add_roles(role, reason=f"Added by {ctx.author}")
await ctx.send(f"Gave {role.name} to {member.mention}")
@role.command()
async def remove(ctx, member: disnake.Member, role: disnake.Role):
"""Remove a role from a member."""
if role >= ctx.author.top_role:
return await ctx.send("You cannot manage this role.")
if role not in member.roles:
return await ctx.send(f"{member} doesn't have the {role.name} role.")
await member.remove_roles(role, reason=f"Removed by {ctx.author}")
await ctx.send(f"Removed {role.name} from {member.mention}")from typing import Union, Optional, Literal
@bot.command()
async def kick(
ctx,
member: disnake.Member,
*,
reason: Optional[str] = "No reason provided"
):
"""Kick a member from the server."""
if not ctx.author.guild_permissions.kick_members:
return await ctx.send("You don't have permission to kick members.")
if member.top_role >= ctx.author.top_role:
return await ctx.send("You cannot kick this member.")
await member.kick(reason=reason)
await ctx.send(f"Kicked {member} for: {reason}")
@bot.command()
async def ban(
ctx,
user: Union[disnake.Member, disnake.User],
delete_days: Optional[int] = 1,
*,
reason: Optional[str] = "No reason provided"
):
"""Ban a user from the server."""
if not ctx.author.guild_permissions.ban_members:
return await ctx.send("You don't have permission to ban members.")
if isinstance(user, disnake.Member) and user.top_role >= ctx.author.top_role:
return await ctx.send("You cannot ban this member.")
if not 0 <= delete_days <= 7:
return await ctx.send("Delete days must be between 0 and 7.")
await ctx.guild.ban(user, delete_message_days=delete_days, reason=reason)
await ctx.send(f"Banned {user} for: {reason}")
@bot.command()
async def purge(
ctx,
limit: int = 10,
target: Union[disnake.Member, disnake.User, str, None] = None
):
"""Purge messages from channel."""
if not ctx.author.guild_permissions.manage_messages:
return await ctx.send("You don't have permission to purge messages.")
if limit > 100:
return await ctx.send("Cannot purge more than 100 messages at once.")
def message_check(message):
if target is None:
return True
elif isinstance(target, (disnake.Member, disnake.User)):
return message.author == target
elif isinstance(target, str):
return target.lower() in message.content.lower()
return False
deleted = await ctx.channel.purge(limit=limit, check=message_check)
await ctx.send(f"Purged {len(deleted)} messages.", delete_after=5)
@bot.command()
async def remind(
ctx,
time: str,
*,
reminder: str
):
"""Set a reminder (e.g., !remind 1h Take break)."""
import re
from datetime import datetime, timedelta
# Parse time string
time_regex = re.compile(r'(\d+)([smhd])')
matches = time_regex.findall(time.lower())
if not matches:
return await ctx.send("Invalid time format. Use: 1s, 1m, 1h, 1d")
total_seconds = 0
for amount, unit in matches:
amount = int(amount)
if unit == 's':
total_seconds += amount
elif unit == 'm':
total_seconds += amount * 60
elif unit == 'h':
total_seconds += amount * 3600
elif unit == 'd':
total_seconds += amount * 86400
if total_seconds > 86400 * 30: # 30 days max
return await ctx.send("Reminder cannot be longer than 30 days.")
# Schedule reminder (in real bot, use a task scheduler)
reminder_time = datetime.utcnow() + timedelta(seconds=total_seconds)
await ctx.send(f"Reminder set for <t:{int(reminder_time.timestamp())}:F>")
# Simple sleep for demo (use proper task scheduling in production)
await asyncio.sleep(total_seconds)
try:
await ctx.author.send(f"⏰ Reminder: {reminder}")
except disnake.Forbidden:
await ctx.send(f"⏰ {ctx.author.mention}, reminder: {reminder}")
# Custom converter example
class ColorConverter(commands.Converter):
async def convert(self, ctx, argument):
# Try hex color
if argument.startswith('#'):
try:
return disnake.Color(int(argument[1:], 16))
except ValueError:
pass
# Try color name
try:
return getattr(disnake.Color, argument.lower())()
except AttributeError:
pass
# Try RGB values
if ',' in argument:
try:
r, g, b = map(int, argument.split(','))
return disnake.Color.from_rgb(r, g, b)
except ValueError:
pass
raise commands.BadArgument(f"Could not convert '{argument}' to a color.")
@bot.command()
async def color(ctx, color: ColorConverter):
"""Test color conversion."""
embed = disnake.Embed(title="Color Test", color=color)
embed.add_field(name="RGB", value=f"{color.r}, {color.g}, {color.b}")
embed.add_field(name="Hex", value=f"#{color.value:06x}")
await ctx.send(embed=embed)
# Flag system example
class ModFlags(commands.FlagConverter):
silent: bool = commands.flag(default=False, description="Don't send notification")
reason: Optional[str] = commands.flag(default=None, description="Reason for action")
duration: Optional[str] = commands.flag(default=None, description="Duration for temporary actions")
@bot.command()
async def tempban(ctx, member: disnake.Member, **flags: ModFlags):
"""Temporarily ban a member with flags."""
if not ctx.author.guild_permissions.ban_members:
return await ctx.send("You don't have permission to ban members.")
duration = flags.duration or "1d"
reason = flags.reason or "No reason provided"
silent = flags.silent
# Parse duration and implement temp ban logic
await ctx.send(f"Temp banned {member} for {duration}. Reason: {reason}. Silent: {silent}")# cogs/moderation.py
class Moderation(commands.Cog):
"""Moderation commands and features."""
def __init__(self, bot):
self.bot = bot
self.muted_members = set() # In production, use database
async def cog_check(self, ctx):
"""Check if user has moderation permissions."""
return ctx.author.guild_permissions.manage_messages
async def cog_command_error(self, ctx, error):
"""Handle cog command errors."""
if isinstance(error, commands.CheckFailure):
await ctx.send("You need moderation permissions to use this command.")
else:
# Re-raise for global handler
raise error
@commands.Cog.listener()
async def on_message(self, message):
"""Monitor messages for auto-moderation."""
if message.author.bot or not message.guild:
return
# Auto-delete messages with too many mentions
if len(message.mentions) > 5:
await message.delete()
await message.channel.send(
f"{message.author.mention}, please don't spam mentions.",
delete_after=5
)
@commands.Cog.listener()
async def on_member_join(self, member):
"""Auto-mute members if they were previously muted."""
if member.id in self.muted_members:
mute_role = disnake.utils.get(member.guild.roles, name="Muted")
if mute_role:
await member.add_roles(mute_role)
@commands.command()
async def warn(self, ctx, member: disnake.Member, *, reason):
"""Warn a member."""
# In production, save to database
embed = disnake.Embed(
title="Member Warned",
description=f"{member.mention} has been warned.",
color=0xffaa00
)
embed.add_field(name="Reason", value=reason)
embed.add_field(name="Moderator", value=ctx.author.mention)
await ctx.send(embed=embed)
try:
await member.send(f"You have been warned in {ctx.guild.name}: {reason}")
except disnake.Forbidden:
pass
@commands.command()
async def mute(self, ctx, member: disnake.Member, duration: str = "10m", *, reason="No reason"):
"""Mute a member."""
mute_role = disnake.utils.get(ctx.guild.roles, name="Muted")
if not mute_role:
# Create mute role
mute_role = await ctx.guild.create_role(
name="Muted",
permissions=disnake.Permissions(send_messages=False, speak=False)
)
# Set permissions for all channels
for channel in ctx.guild.channels:
await channel.set_permissions(
mute_role,
send_messages=False,
speak=False
)
await member.add_roles(mute_role, reason=reason)
self.muted_members.add(member.id)
await ctx.send(f"Muted {member} for {duration}. Reason: {reason}")
# Schedule unmute (simplified - use proper task scheduler)
# parse_duration(duration) would return seconds
# await asyncio.sleep(seconds)
# await member.remove_roles(mute_role)
@commands.command()
async def unmute(self, ctx, member: disnake.Member):
"""Unmute a member."""
mute_role = disnake.utils.get(ctx.guild.roles, name="Muted")
if not mute_role or mute_role not in member.roles:
return await ctx.send("Member is not muted.")
await member.remove_roles(mute_role)
self.muted_members.discard(member.id)
await ctx.send(f"Unmuted {member}")
@commands.group()
async def automod(self, ctx):
"""Auto-moderation settings."""
if ctx.invoked_subcommand is None:
await ctx.send_help(ctx.command)
@automod.command()
async def spam(self, ctx, enabled: bool):
"""Toggle spam protection."""
# Save setting to database
await ctx.send(f"Spam protection {'enabled' if enabled else 'disabled'}.")
# cogs/fun.py
class Fun(commands.Cog):
"""Fun and entertainment commands."""
def __init__(self, bot):
self.bot = bot
@commands.command()
@commands.cooldown(1, 5, commands.BucketType.user)
async def roll(self, ctx, dice: str = "1d6"):
"""Roll dice (e.g., 1d6, 2d20)."""
import random
import re
match = re.match(r'(\d+)d(\d+)', dice)
if not match:
return await ctx.send("Invalid dice format. Use format like: 1d6, 2d20")
count, sides = map(int, match.groups())
if count > 10 or sides > 100:
return await ctx.send("Too many dice or sides!")
rolls = [random.randint(1, sides) for _ in range(count)]
result = sum(rolls)
embed = disnake.Embed(title=f"🎲 Dice Roll: {dice}")
embed.add_field(name="Rolls", value=" + ".join(map(str, rolls)))
embed.add_field(name="Total", value=result)
await ctx.send(embed=embed)
@commands.command()
async def choose(self, ctx, *choices):
"""Choose randomly from options."""
if len(choices) < 2:
return await ctx.send("Please provide at least 2 choices.")
choice = random.choice(choices)
await ctx.send(f"I choose: **{choice}**")
@commands.command()
async def coinflip(self, ctx):
"""Flip a coin."""
result = random.choice(["Heads", "Tails"])
emoji = "🟡" if result == "Heads" else "⚫"
await ctx.send(f"{emoji} **{result}**!")
# cogs/utility.py
class Utility(commands.Cog):
"""Utility commands."""
def __init__(self, bot):
self.bot = bot
@commands.command()
async def serverinfo(self, ctx):
"""Show server information."""
guild = ctx.guild
embed = disnake.Embed(title=guild.name, color=0x00ff00)
embed.set_thumbnail(url=guild.icon.url if guild.icon else None)
embed.add_field(name="Owner", value=guild.owner.mention)
embed.add_field(name="Created", value=f"<t:{int(guild.created_at.timestamp())}:F>")
embed.add_field(name="Members", value=guild.member_count)
embed.add_field(name="Channels", value=len(guild.channels))
embed.add_field(name="Roles", value=len(guild.roles))
embed.add_field(name="Boost Level", value=guild.premium_tier)
await ctx.send(embed=embed)
@commands.command()
async def avatar(self, ctx, user: disnake.User = None):
"""Show user avatar."""
user = user or ctx.author
embed = disnake.Embed(title=f"{user}'s Avatar")
embed.set_image(url=user.display_avatar.url)
await ctx.send(embed=embed)
@commands.command()
async def poll(self, ctx, question, *options):
"""Create a poll with reactions."""
if len(options) > 10:
return await ctx.send("Too many options! Maximum 10.")
if len(options) < 2:
return await ctx.send("Need at least 2 options!")
reactions = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟']
embed = disnake.Embed(title=f"📊 {question}", color=0x0099ff)
for i, option in enumerate(options):
embed.add_field(
name=f"{reactions[i]} Option {i+1}",
value=option,
inline=False
)
message = await ctx.send(embed=embed)
for i in range(len(options)):
await message.add_reaction(reactions[i])
# Load cogs
async def setup(bot):
await bot.add_cog(Moderation(bot))
await bot.add_cog(Fun(bot))
await bot.add_cog(Utility(bot))
# In main bot file
async def load_extensions():
initial_extensions = [
'cogs.moderation',
'cogs.fun',
'cogs.utility'
]
for extension in initial_extensions:
try:
await bot.load_extension(extension)
print(f'Loaded {extension}')
except Exception as e:
print(f'Failed to load {extension}: {e}')
# Extension management commands
@bot.command()
@commands.is_owner()
async def load(ctx, extension):
"""Load an extension."""
try:
await bot.load_extension(f'cogs.{extension}')
await ctx.send(f'✅ Loaded {extension}')
except Exception as e:
await ctx.send(f'❌ Failed to load {extension}: {e}')
@bot.command()
@commands.is_owner()
async def unload(ctx, extension):
"""Unload an extension."""
try:
await bot.unload_extension(f'cogs.{extension}')
await ctx.send(f'✅ Unloaded {extension}')
except Exception as e:
await ctx.send(f'❌ Failed to unload {extension}: {e}')
@bot.command()
@commands.is_owner()
async def reload(ctx, extension):
"""Reload an extension."""
try:
await bot.reload_extension(f'cogs.{extension}')
await ctx.send(f'🔄 Reloaded {extension}')
except Exception as e:
await ctx.send(f'❌ Failed to reload {extension}: {e}')
# Setup hook
async def main():
await load_extensions()
await bot.start('YOUR_BOT_TOKEN')
if __name__ == '__main__':
asyncio.run(main())Install with Tessl CLI
npx tessl i tessl/pypi-disnake