or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-commands.mdchannels.mdclient.mdcommands.mderrors.mdevents.mdguild.mdindex.mdmessages.mdpermissions.mdtasks.mdui.mdusers.mdutilities.mdvoice.mdwebhooks.md

commands.mddocs/

0

# Nextcord Command Framework

1

2

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

3

4

## Bot Class

5

6

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

7

8

### Bot Constructor and Setup { .api }

9

10

```python

11

import nextcord

12

from nextcord.ext import commands

13

from nextcord.ext.commands import Bot, AutoShardedBot, Context

14

from typing import Optional, List, Union, Callable, Any

15

16

class Bot(commands.Bot):

17

"""A subclass of nextcord.Client that has command functionality.

18

19

This allows for a prefix-based command system alongside application commands.

20

21

Attributes

22

----------

23

command_prefix: Union[str, List[str], Callable]

24

The prefix(es) that the bot will respond to.

25

case_insensitive: bool

26

Whether commands are case insensitive.

27

description: Optional[str]

28

The bot's description.

29

help_command: Optional[HelpCommand]

30

The help command implementation.

31

owner_id: Optional[int]

32

The bot owner's user ID.

33

owner_ids: Optional[Set[int]]

34

Set of bot owner user IDs.

35

"""

36

37

def __init__(

38

self,

39

command_prefix: Union[str, List[str], Callable],

40

*,

41

help_command: Optional[commands.HelpCommand] = commands.DefaultHelpCommand(),

42

description: Optional[str] = None,

43

intents: nextcord.Intents = nextcord.Intents.default(),

44

case_insensitive: bool = False,

45

strip_after_prefix: bool = False,

46

**options

47

):

48

"""Initialize the bot.

49

50

Parameters

51

----------

52

command_prefix: Union[str, List[str], Callable]

53

The prefix that will trigger bot commands.

54

help_command: Optional[commands.HelpCommand]

55

The help command implementation. Pass None to disable.

56

description: Optional[str]

57

A description for the bot.

58

intents: nextcord.Intents

59

Gateway intents for the bot.

60

case_insensitive: bool

61

Whether commands should be case insensitive.

62

strip_after_prefix: bool

63

Whether to strip whitespace after the prefix.

64

**options

65

Additional options passed to the Client constructor.

66

"""

67

...

68

69

async def get_prefix(self, message: nextcord.Message) -> Union[str, List[str]]:

70

"""Get the prefix for a message.

71

72

This can be overridden for dynamic prefixes.

73

74

Parameters

75

----------

76

message: nextcord.Message

77

The message to get the prefix for.

78

79

Returns

80

-------

81

Union[str, List[str]]

82

The prefix(es) for this message.

83

"""

84

...

85

86

async def get_context(

87

self,

88

message: nextcord.Message,

89

*,

90

cls: type = Context

91

) -> Context:

92

"""Get the command context from a message.

93

94

Parameters

95

----------

96

message: nextcord.Message

97

The message to create context from.

98

cls: type

99

The context class to use.

100

101

Returns

102

-------

103

Context

104

The command context.

105

"""

106

...

107

108

# Basic bot setup

109

bot = commands.Bot(

110

command_prefix='!',

111

description='A helpful bot',

112

intents=nextcord.Intents.default(),

113

case_insensitive=True

114

)

115

116

# Dynamic prefix example

117

async def get_prefix(bot, message):

118

"""Dynamic prefix function."""

119

# Default prefixes

120

prefixes = ['!', '?']

121

122

# Add custom guild prefixes (from database)

123

if message.guild:

124

# This would typically query a database

125

guild_prefix = get_guild_prefix(message.guild.id) # Implement this

126

if guild_prefix:

127

prefixes.append(guild_prefix)

128

129

# Allow bot mention as prefix

130

return commands.when_mentioned_or(*prefixes)(bot, message)

131

132

# Bot with dynamic prefix

133

bot = commands.Bot(command_prefix=get_prefix)

134

135

@bot.event

136

async def on_ready():

137

"""Bot ready event."""

138

print(f'{bot.user} has connected to Discord!')

139

print(f'Bot is in {len(bot.guilds)} guilds')

140

141

@bot.event

142

async def on_message(message):

143

"""Process messages for commands."""

144

# Ignore bot messages

145

if message.author.bot:

146

return

147

148

# Process commands

149

await bot.process_commands(message)

150

```

151

152

## Commands

153

154

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

155

156

### Command Decorator { .api }

157

158

```python

159

def command(

160

name: Optional[str] = None,

161

*,

162

cls: Optional[type] = None,

163

**attrs

164

) -> Callable:

165

"""Decorator to create a command.

166

167

Parameters

168

----------

169

name: Optional[str]

170

The name of the command. If not given, uses the function name.

171

cls: Optional[type]

172

The class to construct the command with.

173

**attrs

174

Additional attributes for the command.

175

"""

176

...

177

178

# Basic command examples

179

@bot.command()

180

async def ping(ctx):

181

"""Check the bot's latency."""

182

latency = round(bot.latency * 1000)

183

await ctx.send(f'Pong! {latency}ms')

184

185

@bot.command(name='hello', aliases=['hi', 'hey'])

186

async def greet(ctx, *, name: str = None):

187

"""Greet a user.

188

189

Parameters

190

----------

191

ctx: commands.Context

192

The command context.

193

name: str, optional

194

The name to greet. If not provided, greets the command author.

195

"""

196

target = name or ctx.author.display_name

197

await ctx.send(f'Hello, {target}!')

198

199

@bot.command()

200

async def userinfo(ctx, member: nextcord.Member = None):

201

"""Get information about a user.

202

203

Parameters

204

----------

205

ctx: commands.Context

206

The command context.

207

member: nextcord.Member, optional

208

The member to get information about. Defaults to command author.

209

"""

210

user = member or ctx.author

211

212

embed = nextcord.Embed(

213

title=f"User Info: {user.display_name}",

214

color=user.color or nextcord.Color.blue()

215

)

216

217

embed.set_thumbnail(url=user.display_avatar.url)

218

embed.add_field(name="Username", value=str(user), inline=True)

219

embed.add_field(name="ID", value=user.id, inline=True)

220

embed.add_field(name="Joined", value=user.joined_at.strftime("%Y-%m-%d") if user.joined_at else "Unknown", inline=True)

221

222

await ctx.send(embed=embed)

223

224

# Command with multiple parameters

225

@bot.command()

226

async def ban(ctx, member: nextcord.Member, *, reason: str = "No reason provided"):

227

"""Ban a member from the server.

228

229

Parameters

230

----------

231

ctx: commands.Context

232

The command context.

233

member: nextcord.Member

234

The member to ban.

235

reason: str

236

The reason for the ban.

237

"""

238

# Check permissions

239

if not ctx.author.guild_permissions.ban_members:

240

await ctx.send("❌ You don't have permission to ban members.")

241

return

242

243

try:

244

await member.ban(reason=f"{reason} | Banned by {ctx.author}")

245

await ctx.send(f"✅ Banned {member.mention} for: {reason}")

246

except nextcord.Forbidden:

247

await ctx.send("❌ I don't have permission to ban this member.")

248

except nextcord.HTTPException:

249

await ctx.send("❌ Failed to ban member.")

250

251

# Command with converters

252

@bot.command()

253

async def role_info(ctx, role: nextcord.Role):

254

"""Get information about a role.

255

256

Parameters

257

----------

258

ctx: commands.Context

259

The command context.

260

role: nextcord.Role

261

The role to get information about.

262

"""

263

embed = nextcord.Embed(

264

title=f"Role Info: {role.name}",

265

color=role.color

266

)

267

268

embed.add_field(name="ID", value=role.id, inline=True)

269

embed.add_field(name="Position", value=role.position, inline=True)

270

embed.add_field(name="Members", value=len(role.members), inline=True)

271

embed.add_field(name="Mentionable", value="Yes" if role.mentionable else "No", inline=True)

272

embed.add_field(name="Hoisted", value="Yes" if role.hoist else "No", inline=True)

273

embed.add_field(name="Managed", value="Yes" if role.managed else "No", inline=True)

274

275

if role.permissions.administrator:

276

embed.add_field(name="⚠️ Warning", value="This role has Administrator permission!", inline=False)

277

278

await ctx.send(embed=embed)

279

```

280

281

### Command Context { .api }

282

283

```python

284

class Context:

285

"""The context for a command.

286

287

Provides information about the command invocation and convenience methods.

288

289

Attributes

290

----------

291

message: nextcord.Message

292

The message that triggered the command.

293

bot: Bot

294

The bot instance.

295

args: List[Any]

296

The arguments passed to the command.

297

kwargs: Dict[str, Any]

298

The keyword arguments passed to the command.

299

prefix: str

300

The prefix used to invoke the command.

301

command: Command

302

The command that was invoked.

303

invoked_with: str

304

The alias used to invoke the command.

305

invoked_parents: List[str]

306

The parent commands used to invoke a subcommand.

307

invoked_subcommand: Optional[Command]

308

The subcommand that was invoked.

309

subcommand_passed: Optional[str]

310

The string passed after a group command.

311

guild: Optional[nextcord.Guild]

312

The guild the command was used in.

313

channel: Union[nextcord.abc.Messageable]

314

The channel the command was used in.

315

author: Union[nextcord.Member, nextcord.User]

316

The user who invoked the command.

317

me: Union[nextcord.Member, nextcord.ClientUser]

318

The bot's member object in the guild.

319

"""

320

321

async def send(

322

self,

323

content: Optional[str] = None,

324

*,

325

tts: bool = False,

326

embed: Optional[nextcord.Embed] = None,

327

embeds: Optional[List[nextcord.Embed]] = None,

328

file: Optional[nextcord.File] = None,

329

files: Optional[List[nextcord.File]] = None,

330

stickers: Optional[List[nextcord.GuildSticker]] = None,

331

delete_after: Optional[float] = None,

332

nonce: Optional[Union[str, int]] = None,

333

allowed_mentions: Optional[nextcord.AllowedMentions] = None,

334

reference: Optional[Union[nextcord.Message, nextcord.MessageReference]] = None,

335

mention_author: Optional[bool] = None,

336

view: Optional[nextcord.ui.View] = None,

337

suppress_embeds: bool = False,

338

) -> nextcord.Message:

339

"""Send a message to the channel.

340

341

This is a shortcut for ctx.channel.send().

342

343

Parameters are the same as nextcord.abc.Messageable.send().

344

345

Returns

346

-------

347

nextcord.Message

348

The message that was sent.

349

"""

350

...

351

352

async def reply(

353

self,

354

content: Optional[str] = None,

355

**kwargs

356

) -> nextcord.Message:

357

"""Reply to the message that invoked the command.

358

359

Parameters are the same as ctx.send().

360

361

Returns

362

-------

363

nextcord.Message

364

The message that was sent.

365

"""

366

...

367

368

def typing(self):

369

"""Return a typing context manager."""

370

return self.channel.typing()

371

372

@property

373

def valid(self) -> bool:

374

"""bool: Whether the context is valid for command processing."""

375

...

376

377

@property

378

def clean_prefix(self) -> str:

379

"""str: The cleaned up invoke prefix."""

380

...

381

382

@property

383

def cog(self) -> Optional["Cog"]:

384

"""Optional[Cog]: The cog the command belongs to."""

385

...

386

387

# Context usage examples

388

@bot.command()

389

async def ctx_demo(ctx):

390

"""Demonstrate context usage."""

391

embed = nextcord.Embed(title="Context Information")

392

393

embed.add_field(name="Author", value=ctx.author.mention, inline=True)

394

embed.add_field(name="Channel", value=ctx.channel.mention, inline=True)

395

embed.add_field(name="Guild", value=ctx.guild.name if ctx.guild else "DM", inline=True)

396

embed.add_field(name="Prefix", value=ctx.prefix, inline=True)

397

embed.add_field(name="Command", value=ctx.command.name, inline=True)

398

embed.add_field(name="Invoked With", value=ctx.invoked_with, inline=True)

399

400

await ctx.send(embed=embed)

401

402

@bot.command()

403

async def slow_command(ctx):

404

"""A command that takes some time to process."""

405

async with ctx.typing():

406

# Simulate slow processing

407

import asyncio

408

await asyncio.sleep(3)

409

await ctx.send("Processing complete!")

410

411

@bot.command()

412

async def reply_demo(ctx):

413

"""Demonstrate reply functionality."""

414

await ctx.reply("This is a reply to your command!")

415

416

# Custom context class

417

class CustomContext(commands.Context):

418

"""Custom context with additional features."""

419

420

async def send_success(self, message: str):

421

"""Send a success message."""

422

embed = nextcord.Embed(

423

title="✅ Success",

424

description=message,

425

color=nextcord.Color.green()

426

)

427

await self.send(embed=embed)

428

429

async def send_error(self, message: str):

430

"""Send an error message."""

431

embed = nextcord.Embed(

432

title="❌ Error",

433

description=message,

434

color=nextcord.Color.red()

435

)

436

await self.send(embed=embed)

437

438

async def confirm(self, message: str, timeout: float = 30.0) -> bool:

439

"""Ask for user confirmation."""

440

embed = nextcord.Embed(

441

title="Confirmation Required",

442

description=f"{message}\n\nReact with ✅ to confirm or ❌ to cancel.",

443

color=nextcord.Color.orange()

444

)

445

446

msg = await self.send(embed=embed)

447

await msg.add_reaction("✅")

448

await msg.add_reaction("❌")

449

450

def check(reaction, user):

451

return (

452

user == self.author and

453

reaction.message.id == msg.id and

454

str(reaction.emoji) in ["✅", "❌"]

455

)

456

457

try:

458

reaction, user = await self.bot.wait_for('reaction_add', check=check, timeout=timeout)

459

return str(reaction.emoji) == "✅"

460

except asyncio.TimeoutError:

461

await msg.edit(embed=nextcord.Embed(

462

title="Confirmation Timed Out",

463

description="No response received within 30 seconds.",

464

color=nextcord.Color.red()

465

))

466

return False

467

468

# Use custom context

469

async def get_context(message, *, cls=CustomContext):

470

return await super(Bot, bot).get_context(message, cls=cls)

471

472

bot.get_context = get_context

473

474

@bot.command()

475

async def delete_all_messages(ctx):

476

"""Delete all messages (with confirmation)."""

477

confirmed = await ctx.confirm("Are you sure you want to delete all messages? This cannot be undone!")

478

479

if confirmed:

480

# Perform the dangerous action

481

await ctx.send_success("Messages deleted successfully!")

482

else:

483

await ctx.send("Operation cancelled.")

484

```

485

486

## Command Groups

487

488

Hierarchical command organization with subcommands and groups.

489

490

### Group Commands { .api }

491

492

```python

493

def group(

494

name: Optional[str] = None,

495

*,

496

cls: Optional[type] = None,

497

invoke_without_command: bool = False,

498

**attrs

499

) -> Callable:

500

"""Decorator to create a command group.

501

502

Parameters

503

----------

504

name: Optional[str]

505

The name of the group.

506

cls: Optional[type]

507

The class to construct the group with.

508

invoke_without_command: bool

509

Whether the group can be invoked without a subcommand.

510

**attrs

511

Additional attributes for the group.

512

"""

513

...

514

515

class Group(commands.Command):

516

"""A command that can contain subcommands.

517

518

Attributes

519

----------

520

invoke_without_command: bool

521

Whether this group can be invoked without a subcommand.

522

commands: Dict[str, Command]

523

The subcommands in this group.

524

"""

525

526

def add_command(self, command: commands.Command) -> None:

527

"""Add a subcommand to this group."""

528

...

529

530

def remove_command(self, name: str) -> Optional[commands.Command]:

531

"""Remove a subcommand from this group."""

532

...

533

534

def get_command(self, name: str) -> Optional[commands.Command]:

535

"""Get a subcommand by name."""

536

...

537

538

# Group command examples

539

@bot.group(invoke_without_command=True)

540

async def config(ctx):

541

"""Server configuration commands.

542

543

Use subcommands to configure various aspects of the server.

544

"""

545

if ctx.invoked_subcommand is None:

546

embed = nextcord.Embed(

547

title="Server Configuration",

548

description="Available configuration options:",

549

color=nextcord.Color.blue()

550

)

551

552

embed.add_field(

553

name="Subcommands",

554

value=(

555

"`!config prefix <new_prefix>` - Change bot prefix\n"

556

"`!config welcome <channel>` - Set welcome channel\n"

557

"`!config autorole <role>` - Set auto role for new members\n"

558

"`!config logs <channel>` - Set log channel"

559

),

560

inline=False

561

)

562

563

await ctx.send(embed=embed)

564

565

@config.command()

566

async def prefix(ctx, new_prefix: str):

567

"""Change the bot prefix for this server."""

568

if not ctx.author.guild_permissions.manage_guild:

569

await ctx.send("❌ You need Manage Server permission to change the prefix.")

570

return

571

572

if len(new_prefix) > 5:

573

await ctx.send("❌ Prefix must be 5 characters or less.")

574

return

575

576

# Save to database (implement this)

577

save_guild_prefix(ctx.guild.id, new_prefix)

578

579

await ctx.send(f"✅ Bot prefix changed to `{new_prefix}`")

580

581

@config.command()

582

async def welcome(ctx, channel: nextcord.TextChannel):

583

"""Set the welcome channel for new members."""

584

if not ctx.author.guild_permissions.manage_guild:

585

await ctx.send("❌ You need Manage Server permission to set the welcome channel.")

586

return

587

588

# Save to database (implement this)

589

save_welcome_channel(ctx.guild.id, channel.id)

590

591

await ctx.send(f"✅ Welcome channel set to {channel.mention}")

592

593

@config.command()

594

async def autorole(ctx, role: nextcord.Role):

595

"""Set the auto role for new members."""

596

if not ctx.author.guild_permissions.manage_roles:

597

await ctx.send("❌ You need Manage Roles permission to set auto roles.")

598

return

599

600

# Check if bot can assign the role

601

if role.position >= ctx.me.top_role.position:

602

await ctx.send("❌ I cannot assign this role (it's higher than my highest role).")

603

return

604

605

# Save to database (implement this)

606

save_auto_role(ctx.guild.id, role.id)

607

608

await ctx.send(f"✅ Auto role set to {role.mention}")

609

610

# Nested groups

611

@bot.group()

612

async def admin(ctx):

613

"""Admin-only commands."""

614

pass

615

616

@admin.group()

617

async def user(ctx):

618

"""User management commands."""

619

pass

620

621

@user.command()

622

async def ban_user(ctx, member: nextcord.Member, *, reason: str = "No reason provided"):

623

"""Ban a user (admin command)."""

624

# Implementation here

625

await ctx.send(f"Banned {member.mention} for: {reason}")

626

627

@user.command()

628

async def unban(ctx, user_id: int):

629

"""Unban a user by ID."""

630

try:

631

user = await bot.fetch_user(user_id)

632

await ctx.guild.unban(user)

633

await ctx.send(f"✅ Unbanned {user}")

634

except nextcord.NotFound:

635

await ctx.send("❌ User not found or not banned.")

636

except nextcord.Forbidden:

637

await ctx.send("❌ I don't have permission to unban users.")

638

```

639

640

## Converters

641

642

Type conversion for command arguments with custom parsing and validation.

643

644

### Built-in Converters { .api }

645

646

```python

647

# Common built-in converters

648

class MemberConverter:

649

"""Converts argument to nextcord.Member."""

650

async def convert(self, ctx: Context, argument: str) -> nextcord.Member:

651

...

652

653

class UserConverter:

654

"""Converts argument to nextcord.User."""

655

async def convert(self, ctx: Context, argument: str) -> nextcord.User:

656

...

657

658

class TextChannelConverter:

659

"""Converts argument to nextcord.TextChannel."""

660

async def convert(self, ctx: Context, argument: str) -> nextcord.TextChannel:

661

...

662

663

class RoleConverter:

664

"""Converts argument to nextcord.Role."""

665

async def convert(self, ctx: Context, argument: str) -> nextcord.Role:

666

...

667

668

class ColourConverter:

669

"""Converts argument to nextcord.Colour."""

670

async def convert(self, ctx: Context, argument: str) -> nextcord.Colour:

671

...

672

673

# Using built-in converters

674

@bot.command()

675

async def give_role(ctx, member: nextcord.Member, role: nextcord.Role):

676

"""Give a role to a member."""

677

if role in member.roles:

678

await ctx.send(f"{member.mention} already has the {role.mention} role.")

679

return

680

681

try:

682

await member.add_roles(role)

683

await ctx.send(f"✅ Gave {role.mention} to {member.mention}")

684

except nextcord.Forbidden:

685

await ctx.send("❌ I don't have permission to assign roles.")

686

687

@bot.command()

688

async def color_role(ctx, role: nextcord.Role, color: nextcord.Colour):

689

"""Change a role's color."""

690

try:

691

await role.edit(color=color)

692

await ctx.send(f"✅ Changed {role.mention} color to {color}")

693

except nextcord.Forbidden:

694

await ctx.send("❌ I don't have permission to edit roles.")

695

696

# Custom converters

697

class DurationConverter(commands.Converter):

698

"""Convert duration strings like '1h30m' to seconds."""

699

700

async def convert(self, ctx: Context, argument: str) -> int:

701

import re

702

703

# Parse duration string (e.g., "1h30m15s")

704

time_regex = re.compile(r"(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?")

705

match = time_regex.match(argument.lower())

706

707

if not match or not any(match.groups()):

708

raise commands.BadArgument(f"Invalid duration format: {argument}")

709

710

hours, minutes, seconds = match.groups()

711

total_seconds = 0

712

713

if hours:

714

total_seconds += int(hours) * 3600

715

if minutes:

716

total_seconds += int(minutes) * 60

717

if seconds:

718

total_seconds += int(seconds)

719

720

if total_seconds <= 0:

721

raise commands.BadArgument("Duration must be greater than 0")

722

723

return total_seconds

724

725

class TemperatureConverter(commands.Converter):

726

"""Convert temperature between Celsius and Fahrenheit."""

727

728

async def convert(self, ctx: Context, argument: str) -> dict:

729

import re

730

731

# Match patterns like "25c", "77f", "25°C", "77°F"

732

pattern = r"^(-?\d+(?:\.\d+)?)([cf])$"

733

match = re.match(pattern, argument.lower().replace("°", ""))

734

735

if not match:

736

raise commands.BadArgument(f"Invalid temperature format: {argument}")

737

738

value, unit = match.groups()

739

value = float(value)

740

741

if unit == 'c':

742

celsius = value

743

fahrenheit = (value * 9/5) + 32

744

else: # fahrenheit

745

fahrenheit = value

746

celsius = (value - 32) * 5/9

747

748

return {

749

'celsius': round(celsius, 2),

750

'fahrenheit': round(fahrenheit, 2),

751

'original_unit': unit.upper()

752

}

753

754

# Using custom converters

755

@bot.command()

756

async def timeout(ctx, member: nextcord.Member, duration: DurationConverter):

757

"""Timeout a member for a specified duration."""

758

if not ctx.author.guild_permissions.moderate_members:

759

await ctx.send("❌ You don't have permission to timeout members.")

760

return

761

762

from datetime import datetime, timedelta

763

764

until = datetime.utcnow() + timedelta(seconds=duration)

765

766

try:

767

await member.timeout(until=until)

768

769

# Convert duration back to readable format

770

hours, remainder = divmod(duration, 3600)

771

minutes, seconds = divmod(remainder, 60)

772

773

duration_str = []

774

if hours:

775

duration_str.append(f"{hours}h")

776

if minutes:

777

duration_str.append(f"{minutes}m")

778

if seconds:

779

duration_str.append(f"{seconds}s")

780

781

await ctx.send(f"✅ Timed out {member.mention} for {' '.join(duration_str)}")

782

783

except nextcord.Forbidden:

784

await ctx.send("❌ I don't have permission to timeout this member.")

785

786

@bot.command()

787

async def temp(ctx, temperature: TemperatureConverter):

788

"""Convert temperature between Celsius and Fahrenheit."""

789

embed = nextcord.Embed(title="Temperature Conversion", color=nextcord.Color.blue())

790

791

embed.add_field(name="Celsius", value=f"{temperature['celsius']}°C", inline=True)

792

embed.add_field(name="Fahrenheit", value=f"{temperature['fahrenheit']}°F", inline=True)

793

embed.add_field(name="Original", value=f"Input was in {temperature['original_unit']}", inline=False)

794

795

await ctx.send(embed=embed)

796

797

# Union converters for multiple types

798

from typing import Union

799

800

@bot.command()

801

async def info(ctx, target: Union[nextcord.Member, nextcord.TextChannel, nextcord.Role]):

802

"""Get information about a member, channel, or role."""

803

embed = nextcord.Embed(color=nextcord.Color.blue())

804

805

if isinstance(target, nextcord.Member):

806

embed.title = f"Member: {target.display_name}"

807

embed.add_field(name="ID", value=target.id, inline=True)

808

embed.add_field(name="Joined", value=target.joined_at.strftime("%Y-%m-%d") if target.joined_at else "Unknown", inline=True)

809

embed.set_thumbnail(url=target.display_avatar.url)

810

811

elif isinstance(target, nextcord.TextChannel):

812

embed.title = f"Channel: #{target.name}"

813

embed.add_field(name="ID", value=target.id, inline=True)

814

embed.add_field(name="Category", value=target.category.name if target.category else "None", inline=True)

815

embed.add_field(name="Topic", value=target.topic or "No topic", inline=False)

816

817

elif isinstance(target, nextcord.Role):

818

embed.title = f"Role: @{target.name}"

819

embed.add_field(name="ID", value=target.id, inline=True)

820

embed.add_field(name="Members", value=len(target.members), inline=True)

821

embed.add_field(name="Position", value=target.position, inline=True)

822

embed.color = target.color

823

824

await ctx.send(embed=embed)

825

```

826

827

## Checks

828

829

Permission and condition checking decorators for command access control.

830

831

### Built-in Checks { .api }

832

833

```python

834

from nextcord.ext import commands

835

836

# Permission-based checks

837

@commands.has_permissions(manage_messages=True)

838

@bot.command()

839

async def purge(ctx, amount: int):

840

"""Delete messages (requires manage_messages permission)."""

841

if amount > 100:

842

await ctx.send("❌ Cannot delete more than 100 messages at once.")

843

return

844

845

deleted = await ctx.channel.purge(limit=amount + 1) # +1 for command message

846

await ctx.send(f"✅ Deleted {len(deleted) - 1} messages.", delete_after=5)

847

848

@commands.has_any_role("Admin", "Moderator")

849

@bot.command()

850

async def mod_command(ctx):

851

"""Command available to users with Admin or Moderator role."""

852

await ctx.send("This is a moderator command!")

853

854

@commands.has_role("VIP")

855

@bot.command()

856

async def vip_command(ctx):

857

"""Command available only to VIP members."""

858

await ctx.send("Welcome to the VIP area!")

859

860

# Context-based checks

861

@commands.guild_only()

862

@bot.command()

863

async def server_info(ctx):

864

"""Get server information (guild only)."""

865

guild = ctx.guild

866

867

embed = nextcord.Embed(title=guild.name, color=nextcord.Color.blue())

868

embed.set_thumbnail(url=guild.icon.url if guild.icon else None)

869

870

embed.add_field(name="Members", value=guild.member_count, inline=True)

871

embed.add_field(name="Channels", value=len(guild.channels), inline=True)

872

embed.add_field(name="Roles", value=len(guild.roles), inline=True)

873

embed.add_field(name="Owner", value=guild.owner.mention, inline=True)

874

embed.add_field(name="Created", value=guild.created_at.strftime("%Y-%m-%d"), inline=True)

875

876

await ctx.send(embed=embed)

877

878

@commands.dm_only()

879

@bot.command()

880

async def secret(ctx):

881

"""A command that only works in DMs."""

882

await ctx.send("🤫 This is a secret message that only works in DMs!")

883

884

@commands.is_owner()

885

@bot.command()

886

async def shutdown(ctx):

887

"""Shut down the bot (owner only)."""

888

await ctx.send("Shutting down...")

889

await bot.close()

890

891

# Cooldown checks

892

@commands.cooldown(1, 30.0, commands.BucketType.user)

893

@bot.command()

894

async def daily(ctx):

895

"""Claim daily reward (once per 30 seconds per user)."""

896

# This would typically involve a database

897

reward = 100

898

await ctx.send(f"🎁 You claimed your daily reward of {reward} coins!")

899

900

@commands.cooldown(2, 60.0, commands.BucketType.guild)

901

@bot.command()

902

async def server_command(ctx):

903

"""A command with server-wide cooldown (2 uses per minute per guild)."""

904

await ctx.send("This command has a server-wide cooldown!")

905

906

# Custom checks

907

def is_in_voice():

908

"""Check if user is in a voice channel."""

909

async def predicate(ctx):

910

return ctx.author.voice is not None

911

return commands.check(predicate)

912

913

def bot_has_permissions(**perms):

914

"""Check if bot has specific permissions."""

915

async def predicate(ctx):

916

bot_perms = ctx.channel.permissions_for(ctx.me)

917

missing = [perm for perm, value in perms.items() if getattr(bot_perms, perm) != value]

918

if missing:

919

raise commands.BotMissingPermissions(missing)

920

return True

921

return commands.check(predicate)

922

923

@is_in_voice()

924

@bot.command()

925

async def voice_info(ctx):

926

"""Get information about your current voice channel."""

927

voice = ctx.author.voice

928

channel = voice.channel

929

930

embed = nextcord.Embed(

931

title=f"Voice Channel: {channel.name}",

932

color=nextcord.Color.green()

933

)

934

935

embed.add_field(name="Members", value=len(channel.members), inline=True)

936

embed.add_field(name="Bitrate", value=f"{channel.bitrate // 1000}kbps", inline=True)

937

embed.add_field(name="User Limit", value=channel.user_limit or "No limit", inline=True)

938

939

if voice.self_mute:

940

embed.add_field(name="Status", value="🔇 Self-muted", inline=True)

941

if voice.self_deaf:

942

embed.add_field(name="Status", value="🔇 Self-deafened", inline=True)

943

944

await ctx.send(embed=embed)

945

946

@bot_has_permissions(embed_links=True, attach_files=True)

947

@bot.command()

948

async def fancy_command(ctx):

949

"""A command that requires bot to have embed and file permissions."""

950

embed = nextcord.Embed(title="Fancy Command", description="This requires special permissions!")

951

952

# Create a simple text file

953

import io

954

file_content = "This is a generated file!"

955

file = nextcord.File(io.BytesIO(file_content.encode()), filename="generated.txt")

956

957

await ctx.send(embed=embed, file=file)

958

959

# Global check for all commands

960

@bot.check

961

async def globally_block_dms(ctx):

962

"""Global check to block all commands in DMs."""

963

return ctx.guild is not None

964

965

# Per-command check override

966

@bot.command()

967

@commands.check(lambda ctx: True) # Override global check

968

async def help_dm(ctx):

969

"""Help command that works in DMs despite global check."""

970

await ctx.send("This help command works everywhere!")

971

```

972

973

## Error Handling

974

975

Comprehensive error handling for command execution with custom error responses.

976

977

### Error Handlers { .api }

978

979

```python

980

@bot.event

981

async def on_command_error(ctx, error):

982

"""Global command error handler."""

983

984

# Ignore errors from commands that have local error handlers

985

if hasattr(ctx.command, 'on_error'):

986

return

987

988

# Ignore errors from cogs that have local error handlers

989

if ctx.cog and ctx.cog.has_error_handler():

990

return

991

992

# Handle specific error types

993

if isinstance(error, commands.CommandNotFound):

994

# Optionally ignore or suggest similar commands

995

return

996

997

elif isinstance(error, commands.MissingRequiredArgument):

998

embed = nextcord.Embed(

999

title="❌ Missing Argument",

1000

description=f"Missing required argument: `{error.param.name}`",

1001

color=nextcord.Color.red()

1002

)

1003

1004

# Show command usage

1005

if ctx.command.help:

1006

embed.add_field(name="Usage", value=f"`{ctx.prefix}{ctx.command.qualified_name} {ctx.command.signature}`", inline=False)

1007

embed.add_field(name="Help", value=ctx.command.help, inline=False)

1008

1009

await ctx.send(embed=embed)

1010

1011

elif isinstance(error, commands.BadArgument):

1012

embed = nextcord.Embed(

1013

title="❌ Invalid Argument",

1014

description=str(error),

1015

color=nextcord.Color.red()

1016

)

1017

await ctx.send(embed=embed)

1018

1019

elif isinstance(error, commands.MissingPermissions):

1020

missing_perms = ", ".join(error.missing_permissions)

1021

await ctx.send(f"❌ You're missing required permissions: {missing_perms}")

1022

1023

elif isinstance(error, commands.BotMissingPermissions):

1024

missing_perms = ", ".join(error.missing_permissions)

1025

await ctx.send(f"❌ I'm missing required permissions: {missing_perms}")

1026

1027

elif isinstance(error, commands.NoPrivateMessage):

1028

await ctx.send("❌ This command cannot be used in private messages.")

1029

1030

elif isinstance(error, commands.PrivateMessageOnly):

1031

await ctx.send("❌ This command can only be used in private messages.")

1032

1033

elif isinstance(error, commands.CheckFailure):

1034

await ctx.send("❌ You don't have permission to use this command.")

1035

1036

elif isinstance(error, commands.CommandOnCooldown):

1037

time_left = round(error.retry_after, 1)

1038

await ctx.send(f"⏰ Command is on cooldown. Try again in {time_left} seconds.")

1039

1040

elif isinstance(error, commands.MaxConcurrencyReached):

1041

await ctx.send(f"❌ Too many people are using this command. Try again later.")

1042

1043

elif isinstance(error, commands.CommandInvokeError):

1044

# Handle errors that occur during command execution

1045

original_error = error.original

1046

1047

if isinstance(original_error, nextcord.Forbidden):

1048

await ctx.send("❌ I don't have permission to perform this action.")

1049

elif isinstance(original_error, nextcord.NotFound):

1050

await ctx.send("❌ The requested resource was not found.")

1051

elif isinstance(original_error, nextcord.HTTPException):

1052

await ctx.send("❌ An error occurred while communicating with Discord.")

1053

else:

1054

# Log unexpected errors

1055

print(f"Unexpected error in {ctx.command}: {original_error}")

1056

await ctx.send("❌ An unexpected error occurred. Please try again later.")

1057

1058

else:

1059

# Log unhandled errors

1060

print(f"Unhandled error type: {type(error).__name__}: {error}")

1061

await ctx.send("❌ An error occurred while processing the command.")

1062

1063

# Command-specific error handler

1064

@bot.command()

1065

async def divide(ctx, a: float, b: float):

1066

"""Divide two numbers."""

1067

try:

1068

result = a / b

1069

await ctx.send(f"{a} ÷ {b} = {result}")

1070

except ZeroDivisionError:

1071

await ctx.send("❌ Cannot divide by zero!")

1072

1073

@divide.error

1074

async def divide_error(ctx, error):

1075

"""Local error handler for the divide command."""

1076

if isinstance(error, commands.BadArgument):

1077

await ctx.send("❌ Please provide valid numbers for division.")

1078

1079

# Custom exceptions

1080

class CustomCommandError(commands.CommandError):

1081

"""Base class for custom command errors."""

1082

pass

1083

1084

class InsufficientFundsError(CustomCommandError):

1085

"""Raised when user doesn't have enough money."""

1086

def __init__(self, required: int, available: int):

1087

self.required = required

1088

self.available = available

1089

super().__init__(f"Insufficient funds: need {required}, have {available}")

1090

1091

@bot.command()

1092

async def buy(ctx, item: str, amount: int):

1093

"""Buy an item from the shop."""

1094

# Mock data

1095

user_balance = 50

1096

item_price = 25

1097

total_cost = item_price * amount

1098

1099

if user_balance < total_cost:

1100

raise InsufficientFundsError(total_cost, user_balance)

1101

1102

# Process purchase

1103

await ctx.send(f"✅ Purchased {amount}x {item} for {total_cost} coins!")

1104

1105

@buy.error

1106

async def buy_error(ctx, error):

1107

"""Error handler for buy command."""

1108

if isinstance(error, InsufficientFundsError):

1109

embed = nextcord.Embed(

1110

title="💰 Insufficient Funds",

1111

description=f"You need {error.required} coins but only have {error.available}.",

1112

color=nextcord.Color.red()

1113

)

1114

embed.add_field(name="Shortfall", value=f"{error.required - error.available} coins", inline=True)

1115

await ctx.send(embed=embed)

1116

```

1117

1118

## Advanced Features

1119

1120

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

1121

1122

### Custom Help Command { .api }

1123

1124

```python

1125

class CustomHelpCommand(commands.DefaultHelpCommand):

1126

"""Custom help command with embeds and better formatting."""

1127

1128

def __init__(self):

1129

super().__init__(

1130

command_attrs={

1131

'name': 'help',

1132

'aliases': ['h'],

1133

'help': 'Shows help about the bot, a command, or a category'

1134

}

1135

)

1136

1137

async def send_bot_help(self, mapping):

1138

"""Send help for the entire bot."""

1139

embed = nextcord.Embed(

1140

title="Bot Help",

1141

description="Here are all available commands:",

1142

color=nextcord.Color.blue()

1143

)

1144

1145

for cog, commands in mapping.items():

1146

filtered_commands = await self.filter_commands(commands, sort=True)

1147

if not filtered_commands:

1148

continue

1149

1150

cog_name = getattr(cog, 'qualified_name', 'No Category')

1151

command_list = [f"`{c.name}`" for c in filtered_commands]

1152

1153

embed.add_field(

1154

name=cog_name,

1155

value=" • ".join(command_list) or "No commands",

1156

inline=False

1157

)

1158

1159

embed.set_footer(text=f"Use {self.context.prefix}help <command> for more info on a command.")

1160

1161

channel = self.get_destination()

1162

await channel.send(embed=embed)

1163

1164

async def send_command_help(self, command):

1165

"""Send help for a specific command."""

1166

embed = nextcord.Embed(

1167

title=f"Command: {command.qualified_name}",

1168

description=command.help or "No description available",

1169

color=nextcord.Color.blue()

1170

)

1171

1172

embed.add_field(

1173

name="Usage",

1174

value=f"`{self.context.prefix}{command.qualified_name} {command.signature}`",

1175

inline=False

1176

)

1177

1178

if command.aliases:

1179

embed.add_field(

1180

name="Aliases",

1181

value=", ".join(f"`{alias}`" for alias in command.aliases),

1182

inline=False

1183

)

1184

1185

if isinstance(command, commands.Group):

1186

subcommands = [f"`{c.name}`" for c in command.commands]

1187

if subcommands:

1188

embed.add_field(

1189

name="Subcommands",

1190

value=" • ".join(subcommands),

1191

inline=False

1192

)

1193

1194

channel = self.get_destination()

1195

await channel.send(embed=embed)

1196

1197

async def send_group_help(self, group):

1198

"""Send help for a command group."""

1199

embed = nextcord.Embed(

1200

title=f"Command Group: {group.qualified_name}",

1201

description=group.help or "No description available",

1202

color=nextcord.Color.blue()

1203

)

1204

1205

embed.add_field(

1206

name="Usage",

1207

value=f"`{self.context.prefix}{group.qualified_name} {group.signature}`",

1208

inline=False

1209

)

1210

1211

filtered_commands = await self.filter_commands(group.commands, sort=True)

1212

if filtered_commands:

1213

command_list = []

1214

for command in filtered_commands:

1215

command_list.append(f"`{command.name}` - {command.short_doc or 'No description'}")

1216

1217

embed.add_field(

1218

name="Subcommands",

1219

value="\n".join(command_list),

1220

inline=False

1221

)

1222

1223

channel = self.get_destination()

1224

await channel.send(embed=embed)

1225

1226

# Set custom help command

1227

bot.help_command = CustomHelpCommand()

1228

1229

# Pagination for long help outputs

1230

class PaginatedHelpCommand(commands.HelpCommand):

1231

"""Help command with pagination for large outputs."""

1232

1233

def __init__(self):

1234

super().__init__(

1235

command_attrs={

1236

'name': 'help',

1237

'help': 'Shows help with pagination'

1238

}

1239

)

1240

1241

async def send_bot_help(self, mapping):

1242

"""Send paginated bot help."""

1243

pages = []

1244

1245

for cog, commands in mapping.items():

1246

filtered_commands = await self.filter_commands(commands, sort=True)

1247

if not filtered_commands:

1248

continue

1249

1250

embed = nextcord.Embed(

1251

title=f"Commands: {getattr(cog, 'qualified_name', 'No Category')}",

1252

color=nextcord.Color.blue()

1253

)

1254

1255

for command in filtered_commands:

1256

embed.add_field(

1257

name=f"{self.context.prefix}{command.qualified_name}",

1258

value=command.short_doc or "No description",

1259

inline=False

1260

)

1261

1262

pages.append(embed)

1263

1264

if not pages:

1265

await self.get_destination().send("No commands available.")

1266

return

1267

1268

# Simple pagination (you could use a more sophisticated system)

1269

for i, page in enumerate(pages):

1270

page.set_footer(text=f"Page {i+1}/{len(pages)}")

1271

await self.get_destination().send(embed=page)

1272

1273

# Dynamic command loading

1274

async def load_extension_command(ctx, extension: str):

1275

"""Dynamically load a bot extension."""

1276

try:

1277

bot.load_extension(f"cogs.{extension}")

1278

await ctx.send(f"✅ Loaded extension: {extension}")

1279

except commands.ExtensionError as e:

1280

await ctx.send(f"❌ Failed to load extension: {e}")

1281

1282

async def unload_extension_command(ctx, extension: str):

1283

"""Dynamically unload a bot extension."""

1284

try:

1285

bot.unload_extension(f"cogs.{extension}")

1286

await ctx.send(f"✅ Unloaded extension: {extension}")

1287

except commands.ExtensionError as e:

1288

await ctx.send(f"❌ Failed to unload extension: {e}")

1289

1290

async def reload_extension_command(ctx, extension: str):

1291

"""Dynamically reload a bot extension."""

1292

try:

1293

bot.reload_extension(f"cogs.{extension}")

1294

await ctx.send(f"✅ Reloaded extension: {extension}")

1295

except commands.ExtensionError as e:

1296

await ctx.send(f"❌ Failed to reload extension: {e}")

1297

```

1298

1299

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.