or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-commands.mdautomod.mdchannels-messaging.mdclient-bot.mdcommand-framework.mderror-handling.mdevents-gateway.mdguild-management.mdindex.mdinteractions-ui.mdlocalization-i18n.mdpermissions-security.mdpolls.mdusers-members.mdvoice-audio.md

command-framework.mddocs/

0

# Command Framework

1

2

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.

3

4

## Capabilities

5

6

### Bot Classes

7

8

Command-enabled bot classes extending basic client functionality with message command processing.

9

10

```python { .api }

11

class Bot(commands.BotBase, Client):

12

def __init__(

13

self,

14

command_prefix: Union[str, Iterable[str], Callable[[Bot, Message], Union[str, Iterable[str]]]],

15

*,

16

help_command: Optional[commands.HelpCommand] = ...,

17

description: Optional[str] = None,

18

**options

19

):

20

"""

21

Initialize a message command bot.

22

23

Parameters:

24

- command_prefix: Command prefix(es) or callable returning prefix(es)

25

- help_command: Custom help command implementation

26

- description: Bot description for help command

27

- options: Additional client options

28

"""

29

30

@property

31

def commands(self) -> Set[commands.Command]:

32

"""All registered commands."""

33

34

@property

35

def cogs(self) -> Mapping[str, commands.Cog]:

36

"""All loaded cogs."""

37

38

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

39

"""

40

Add a command to the bot.

41

42

Parameters:

43

- command: Command to add

44

"""

45

46

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

47

"""

48

Remove a command by name.

49

50

Parameters:

51

- name: Command name

52

53

Returns:

54

Removed command if found

55

"""

56

57

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

58

"""

59

Get a command by name.

60

61

Parameters:

62

- name: Command name (supports subcommands with spaces)

63

64

Returns:

65

Command if found

66

"""

67

68

def walk_commands(self) -> Generator[commands.Command, None, None]:

69

"""

70

Iterate over all commands including subcommands.

71

72

Yields:

73

All registered commands

74

"""

75

76

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

77

"""

78

Get command prefix for a message.

79

80

Parameters:

81

- message: Message to get prefix for

82

83

Returns:

84

Prefix or list of prefixes

85

"""

86

87

async def get_context(self, message: Message, *, cls: Type[Context] = None) -> Context:

88

"""

89

Get command context from a message.

90

91

Parameters:

92

- message: Message to create context from

93

- cls: Custom context class

94

95

Returns:

96

Command context

97

"""

98

99

async def process_commands(self, message: Message) -> None:

100

"""

101

Process a message for commands.

102

103

Parameters:

104

- message: Message to process

105

"""

106

107

async def invoke(self, ctx: commands.Context) -> None:

108

"""

109

Invoke a command from context.

110

111

Parameters:

112

- ctx: Command context

113

"""

114

115

def command(self, name: str = None, **kwargs):

116

"""

117

Decorator for registering commands.

118

119

Parameters:

120

- name: Command name (optional)

121

- kwargs: Command options

122

"""

123

124

def group(self, name: str = None, **kwargs):

125

"""

126

Decorator for registering command groups.

127

128

Parameters:

129

- name: Group name (optional)

130

- kwargs: Group options

131

"""

132

133

def listen(self, name: str = None):

134

"""

135

Decorator for event listeners.

136

137

Parameters:

138

- name: Event name (optional)

139

"""

140

141

def add_cog(self, cog: commands.Cog, *, override: bool = False) -> None:

142

"""

143

Add a cog to the bot.

144

145

Parameters:

146

- cog: Cog instance to add

147

- override: Whether to override existing commands

148

"""

149

150

def remove_cog(self, name: str) -> Optional[commands.Cog]:

151

"""

152

Remove a cog by name.

153

154

Parameters:

155

- name: Cog name

156

157

Returns:

158

Removed cog if found

159

"""

160

161

def get_cog(self, name: str) -> Optional[commands.Cog]:

162

"""

163

Get a cog by name.

164

165

Parameters:

166

- name: Cog name

167

168

Returns:

169

Cog if found

170

"""

171

172

async def load_extension(self, name: str, *, package: Optional[str] = None) -> None:

173

"""

174

Load an extension module.

175

176

Parameters:

177

- name: Extension module name

178

- package: Package name for relative imports

179

"""

180

181

async def unload_extension(self, name: str, *, package: Optional[str] = None) -> None:

182

"""

183

Unload an extension module.

184

185

Parameters:

186

- name: Extension module name

187

- package: Package name for relative imports

188

"""

189

190

async def reload_extension(self, name: str, *, package: Optional[str] = None) -> None:

191

"""

192

Reload an extension module.

193

194

Parameters:

195

- name: Extension module name

196

- package: Package name for relative imports

197

"""

198

199

class AutoShardedBot(commands.AutoShardedBotBase, Bot):

200

"""Auto-sharded message command bot."""

201

202

def __init__(self, command_prefix, **options):

203

"""Initialize auto-sharded command bot."""

204

```

205

206

### Command Objects

207

208

Command representation and execution with parameter handling and validation.

209

210

```python { .api }

211

class Command:

212

def __init__(

213

self,

214

func: Callable,

215

*,

216

name: str = None,

217

aliases: List[str] = None,

218

help: str = None,

219

brief: str = None,

220

usage: str = None,

221

description: str = None,

222

enabled: bool = True,

223

hidden: bool = False,

224

ignore_extra: bool = True,

225

cooldown_after_parsing: bool = False,

226

extras: Dict[str, Any] = None,

227

**kwargs

228

):

229

"""

230

Initialize a command.

231

232

Parameters:

233

- func: Command callback function

234

- name: Command name

235

- aliases: Command aliases

236

- help: Help text

237

- brief: Brief description

238

- usage: Usage syntax

239

- description: Long description

240

- enabled: Whether command is enabled

241

- hidden: Whether to hide from help

242

- ignore_extra: Ignore extra arguments

243

- cooldown_after_parsing: Apply cooldown after parsing

244

- extras: Extra metadata

245

"""

246

247

name: str

248

callback: Callable

249

help: Optional[str]

250

brief: Optional[str]

251

usage: Optional[str]

252

aliases: List[str]

253

enabled: bool

254

hidden: bool

255

checks: List[Check]

256

description: Optional[str]

257

signature: str

258

extras: Dict[str, Any]

259

260

@property

261

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

262

"""Cog the command belongs to."""

263

264

@property

265

def short_doc(self) -> Optional[str]:

266

"""Short documentation string."""

267

268

@property

269

def signature(self) -> str:

270

"""Command signature with parameters."""

271

272

def can_run(self, ctx: Context) -> bool:

273

"""

274

Check if command can be run in context.

275

276

Parameters:

277

- ctx: Command context

278

279

Returns:

280

True if command can run

281

"""

282

283

async def invoke(self, ctx: Context) -> None:

284

"""

285

Invoke the command.

286

287

Parameters:

288

- ctx: Command context

289

"""

290

291

async def reinvoke(self, ctx: Context, *, call_hooks: bool = False) -> None:

292

"""

293

Reinvoke the command bypassing cooldowns.

294

295

Parameters:

296

- ctx: Command context

297

- call_hooks: Whether to call before/after hooks

298

"""

299

300

def error(self, coro):

301

"""

302

Decorator for command error handlers.

303

304

Parameters:

305

- coro: Error handler coroutine

306

"""

307

308

def before_invoke(self, coro):

309

"""

310

Decorator for pre-command hooks.

311

312

Parameters:

313

- coro: Hook coroutine

314

"""

315

316

def after_invoke(self, coro):

317

"""

318

Decorator for post-command hooks.

319

320

Parameters:

321

- coro: Hook coroutine

322

"""

323

324

class Group(Command):

325

"""Command group container for subcommands."""

326

327

def __init__(

328

self,

329

*args,

330

invoke_without_subcommand: bool = False,

331

case_insensitive: bool = False,

332

**kwargs

333

):

334

"""

335

Initialize a command group.

336

337

Parameters:

338

- invoke_without_subcommand: Allow invoking group without subcommand

339

- case_insensitive: Case insensitive subcommand matching

340

"""

341

342

@property

343

def commands(self) -> Set[Command]:

344

"""Subcommands in this group."""

345

346

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

347

"""

348

Add a subcommand.

349

350

Parameters:

351

- command: Subcommand to add

352

"""

353

354

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

355

"""

356

Remove a subcommand.

357

358

Parameters:

359

- name: Subcommand name

360

361

Returns:

362

Removed command if found

363

"""

364

365

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

366

"""

367

Get a subcommand.

368

369

Parameters:

370

- name: Subcommand name

371

372

Returns:

373

Command if found

374

"""

375

376

def walk_commands(self) -> Generator[Command, None, None]:

377

"""

378

Walk all commands in group recursively.

379

380

Yields:

381

All commands and subcommands

382

"""

383

384

def command(self, *args, **kwargs):

385

"""Decorator for adding subcommands."""

386

387

def group(self, *args, **kwargs):

388

"""Decorator for adding subgroups."""

389

```

390

391

### Context Objects

392

393

Command invocation context providing access to message, channel, guild, and bot state.

394

395

```python { .api }

396

class Context:

397

def __init__(

398

self,

399

*,

400

message: Message,

401

bot: Bot,

402

prefix: str = None,

403

command: Command = None,

404

invoked_with: str = None,

405

invoked_parents: List[str] = None,

406

invoked_subcommand: Command = None,

407

subcommand_passed: str = None,

408

command_failed: bool = False,

409

current_parameter: Parameter = None

410

):

411

"""

412

Initialize command context.

413

414

Parameters:

415

- message: Message that triggered command

416

- bot: Bot instance

417

- prefix: Used command prefix

418

- command: Invoked command

419

- invoked_with: Command name used for invocation

420

- invoked_parents: Parent command names

421

- invoked_subcommand: Invoked subcommand

422

- subcommand_passed: Subcommand argument

423

- command_failed: Whether command execution failed

424

- current_parameter: Currently parsing parameter

425

"""

426

427

message: Message

428

bot: Bot

429

command: Optional[Command]

430

invoked_with: Optional[str]

431

invoked_parents: List[str]

432

invoked_subcommand: Optional[Command]

433

subcommand_passed: Optional[str]

434

command_failed: bool

435

prefix: Optional[str]

436

args: List[Any]

437

kwargs: Dict[str, Any]

438

439

@property

440

def author(self) -> Union[User, Member]:

441

"""Message author."""

442

443

@property

444

def guild(self) -> Optional[Guild]:

445

"""Guild where command was invoked."""

446

447

@property

448

def channel(self) -> Messageable:

449

"""Channel where command was invoked."""

450

451

@property

452

def me(self) -> Union[Member, ClientUser]:

453

"""Bot's member object in guild."""

454

455

@property

456

def voice_client(self) -> Optional[VoiceProtocol]:

457

"""Voice client for the guild."""

458

459

@property

460

def valid(self) -> bool:

461

"""Whether context is valid for command invocation."""

462

463

@property

464

def clean_prefix(self) -> str:

465

"""Cleaned command prefix."""

466

467

@property

468

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

469

"""Cog of the invoked command."""

470

471

async def invoke(

472

self,

473

command: Command,

474

*args,

475

**kwargs

476

) -> None:

477

"""

478

Invoke another command.

479

480

Parameters:

481

- command: Command to invoke

482

- args: Command arguments

483

- kwargs: Command keyword arguments

484

"""

485

486

async def reinvoke(self, *, call_hooks: bool = False, restart: bool = True) -> None:

487

"""

488

Reinvoke the current command.

489

490

Parameters:

491

- call_hooks: Whether to call before/after hooks

492

- restart: Whether to restart from beginning

493

"""

494

495

async def send(

496

self,

497

content: str = None,

498

*,

499

tts: bool = False,

500

embed: Embed = None,

501

embeds: List[Embed] = None,

502

file: File = None,

503

files: List[File] = None,

504

allowed_mentions: AllowedMentions = None,

505

reference: Union[Message, MessageReference, PartialMessage] = None,

506

mention_author: bool = None,

507

view: View = None,

508

components: Union[ActionRow, List[ActionRow]] = None,

509

delete_after: float = None,

510

suppress_embeds: bool = False,

511

flags: MessageFlags = None,

512

ephemeral: bool = False

513

) -> Message:

514

"""

515

Send a message to the context channel.

516

517

Parameters:

518

- content: Message content

519

- tts: Text-to-speech

520

- embed: Single embed

521

- embeds: List of embeds

522

- file: Single file

523

- files: List of files

524

- allowed_mentions: Mention configuration

525

- reference: Message to reply to

526

- mention_author: Whether to mention author in reply

527

- view: UI components view

528

- components: Raw components

529

- delete_after: Auto-delete after seconds

530

- suppress_embeds: Suppress embeds

531

- flags: Message flags

532

- ephemeral: Send as ephemeral (slash command contexts only)

533

534

Returns:

535

Sent message

536

"""

537

538

async def reply(self, content: str = None, **kwargs) -> Message:

539

"""

540

Reply to the context message.

541

542

Parameters:

543

- content: Reply content

544

- kwargs: Additional send parameters

545

546

Returns:

547

Sent reply message

548

"""

549

550

def typing(self) -> Typing:

551

"""

552

Context manager for typing indicator.

553

554

Returns:

555

Typing context manager

556

"""

557

558

def history(self, **kwargs) -> AsyncIterator[Message]:

559

"""

560

Get channel message history.

561

562

Parameters:

563

- kwargs: History parameters

564

565

Yields:

566

Messages from channel history

567

"""

568

569

async def fetch_message(self, id: int) -> Message:

570

"""

571

Fetch a message by ID from context channel.

572

573

Parameters:

574

- id: Message ID

575

576

Returns:

577

Message object

578

"""

579

580

def get_parameter(self, name: str) -> Optional[Parameter]:

581

"""

582

Get command parameter by name.

583

584

Parameters:

585

- name: Parameter name

586

587

Returns:

588

Parameter if found

589

"""

590

591

class GuildContext(Context):

592

"""Guild-specific context with additional guild properties."""

593

594

author: Member

595

guild: Guild

596

me: Member

597

598

@property

599

def permissions(self) -> Permissions:

600

"""Author's permissions in channel."""

601

602

@property

603

def bot_permissions(self) -> Permissions:

604

"""Bot's permissions in channel."""

605

```

606

607

### Cog System

608

609

Modular command organization system for grouping related functionality.

610

611

```python { .api }

612

class Cog:

613

"""Base class for command cogs."""

614

615

def __init_subclass__(cls, **kwargs):

616

"""Initialize cog subclass."""

617

618

@classmethod

619

def listener(cls, name: str = None):

620

"""

621

Decorator for event listeners in cogs.

622

623

Parameters:

624

- name: Event name (optional)

625

"""

626

627

def get_commands(self) -> List[Command]:

628

"""

629

Get all commands in this cog.

630

631

Returns:

632

List of cog commands

633

"""

634

635

def walk_commands(self) -> Generator[Command, None, None]:

636

"""

637

Walk all commands in cog.

638

639

Yields:

640

All cog commands

641

"""

642

643

def get_listeners(self) -> List[Tuple[str, Callable]]:

644

"""

645

Get all event listeners in this cog.

646

647

Returns:

648

List of (event_name, callback) tuples

649

"""

650

651

@property

652

def qualified_name(self) -> str:

653

"""Qualified cog name."""

654

655

@property

656

def description(self) -> Optional[str]:

657

"""Cog description."""

658

659

def cog_load(self) -> None:

660

"""Called when cog is loaded."""

661

662

def cog_unload(self) -> None:

663

"""Called when cog is unloaded."""

664

665

async def cog_check(self, ctx: Context) -> bool:

666

"""

667

Global check for all commands in cog.

668

669

Parameters:

670

- ctx: Command context

671

672

Returns:

673

True if commands can run

674

"""

675

676

async def cog_command_error(self, ctx: Context, error: Exception) -> None:

677

"""

678

Error handler for cog commands.

679

680

Parameters:

681

- ctx: Command context

682

- error: Exception raised

683

"""

684

685

async def cog_before_invoke(self, ctx: Context) -> None:

686

"""

687

Called before any cog command is invoked.

688

689

Parameters:

690

- ctx: Command context

691

"""

692

693

async def cog_after_invoke(self, ctx: Context) -> None:

694

"""

695

Called after any cog command is invoked.

696

697

Parameters:

698

- ctx: Command context

699

"""

700

701

def command(name: str = None, **attrs):

702

"""

703

Decorator for creating commands.

704

705

Parameters:

706

- name: Command name

707

- attrs: Command attributes

708

"""

709

710

def group(name: str = None, **attrs):

711

"""

712

Decorator for creating command groups.

713

714

Parameters:

715

- name: Group name

716

- attrs: Group attributes

717

"""

718

719

async def setup(bot: Bot):

720

"""

721

Setup function for extension modules.

722

723

Parameters:

724

- bot: Bot instance

725

"""

726

```

727

728

### Parameter System

729

730

Advanced parameter parsing, type conversion, and validation for command arguments.

731

732

```python { .api }

733

class Parameter:

734

"""Command parameter information."""

735

736

def __init__(

737

self,

738

name: str,

739

kind: inspect.Parameter.kind,

740

*,

741

default: Any = inspect.Parameter.empty,

742

annotation: Any = inspect.Parameter.empty,

743

converter: Converter = None,

744

description: str = None,

745

displayed_default: str = None

746

):

747

"""

748

Initialize parameter.

749

750

Parameters:

751

- name: Parameter name

752

- kind: Parameter kind

753

- default: Default value

754

- annotation: Type annotation

755

- converter: Type converter

756

- description: Parameter description

757

- displayed_default: Default value display

758

"""

759

760

name: str

761

default: Any

762

annotation: Any

763

converter: Converter

764

kind: inspect.Parameter.kind

765

description: Optional[str]

766

displayed_default: Optional[str]

767

768

class Greedy:

769

"""Greedy converter for consuming multiple arguments."""

770

771

def __init__(self, converter: Converter):

772

"""

773

Initialize greedy converter.

774

775

Parameters:

776

- converter: Converter for individual arguments

777

"""

778

779

def param(

780

default: Any = ...,

781

*,

782

converter: Converter = None,

783

description: str = None,

784

displayed_default: str = None,

785

name: str = None

786

) -> Any:

787

"""

788

Configure command parameter.

789

790

Parameters:

791

- default: Default value

792

- converter: Type converter

793

- description: Parameter description

794

- displayed_default: Default display value

795

- name: Parameter name override

796

797

Returns:

798

Configured parameter

799

"""

800

801

# Built-in converters

802

class MemberConverter:

803

"""Convert argument to Member."""

804

805

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

806

"""Convert string to Member."""

807

808

class UserConverter:

809

"""Convert argument to User."""

810

811

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

812

"""Convert string to User."""

813

814

class GuildConverter:

815

"""Convert argument to Guild."""

816

817

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

818

"""Convert string to Guild."""

819

820

class RoleConverter:

821

"""Convert argument to Role."""

822

823

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

824

"""Convert string to Role."""

825

826

class TextChannelConverter:

827

"""Convert argument to TextChannel."""

828

829

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

830

"""Convert string to TextChannel."""

831

832

class VoiceChannelConverter:

833

"""Convert argument to VoiceChannel."""

834

835

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

836

"""Convert string to VoiceChannel."""

837

838

class CategoryChannelConverter:

839

"""Convert argument to CategoryChannel."""

840

841

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

842

"""Convert string to CategoryChannel."""

843

844

class EmojiConverter:

845

"""Convert argument to Emoji."""

846

847

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

848

"""Convert string to Emoji."""

849

850

class PartialEmojiConverter:

851

"""Convert argument to PartialEmoji."""

852

853

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

854

"""Convert string to PartialEmoji."""

855

856

class ColourConverter:

857

"""Convert argument to Colour."""

858

859

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

860

"""Convert string to Colour."""

861

862

class MessageConverter:

863

"""Convert argument to Message."""

864

865

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

866

"""Convert string to Message."""

867

868

class GameConverter:

869

"""Convert argument to Game activity."""

870

871

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

872

"""Convert string to Game."""

873

874

class InviteConverter:

875

"""Convert argument to Invite."""

876

877

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

878

"""Convert string to Invite."""

879

880

class GuildStickerConverter:

881

"""Convert argument to GuildSticker."""

882

883

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

884

"""Convert string to GuildSticker."""

885

886

class ScheduledEventConverter:

887

"""Convert argument to GuildScheduledEvent."""

888

889

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

890

"""Convert string to GuildScheduledEvent."""

891

892

class ThreadConverter:

893

"""Convert argument to Thread."""

894

895

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

896

"""Convert string to Thread."""

897

898

# Union converters

899

typing.Union[Member, User] # Tries Member first, then User

900

typing.Optional[Member] # Optional Member (can be None)

901

typing.Literal['option1', 'option2'] # Literal choices

902

```

903

904

### Checks and Permissions

905

906

Permission checking system for command access control and security.

907

908

```python { .api }

909

def check(predicate: Callable[[Context], bool]):

910

"""

911

Decorator for custom permission checks.

912

913

Parameters:

914

- predicate: Check function

915

"""

916

917

def check_any(*checks: Check):

918

"""

919

Decorator requiring any of the provided checks to pass.

920

921

Parameters:

922

- checks: Check decorators

923

"""

924

925

def has_role(name: Union[str, int]):

926

"""

927

Check if user has specific role.

928

929

Parameters:

930

- name: Role name or ID

931

"""

932

933

def has_any_role(*names: Union[str, int]):

934

"""

935

Check if user has any of the specified roles.

936

937

Parameters:

938

- names: Role names or IDs

939

"""

940

941

def has_permissions(**perms: bool):

942

"""

943

Check if user has specific permissions.

944

945

Parameters:

946

- perms: Permission flags

947

"""

948

949

def has_guild_permissions(**perms: bool):

950

"""

951

Check if user has guild permissions.

952

953

Parameters:

954

- perms: Permission flags

955

"""

956

957

def bot_has_permissions(**perms: bool):

958

"""

959

Check if bot has specific permissions.

960

961

Parameters:

962

- perms: Permission flags

963

"""

964

965

def bot_has_guild_permissions(**perms: bool):

966

"""

967

Check if bot has guild permissions.

968

969

Parameters:

970

- perms: Permission flags

971

"""

972

973

def is_owner():

974

"""Check if user is bot owner."""

975

976

def is_nsfw():

977

"""Check if channel is NSFW."""

978

979

def guild_only():

980

"""Check if command is in a guild."""

981

982

def dm_only():

983

"""Check if command is in DMs."""

984

985

def cooldown(rate: int, per: float, type: BucketType = BucketType.default):

986

"""

987

Apply cooldown to command.

988

989

Parameters:

990

- rate: Number of uses allowed

991

- per: Time period in seconds

992

- type: Cooldown bucket type

993

"""

994

995

def dynamic_cooldown(cooldown: Callable[[Context], Optional[Cooldown]], type: BucketType = BucketType.default):

996

"""

997

Apply dynamic cooldown to command.

998

999

Parameters:

1000

- cooldown: Function returning cooldown configuration

1001

- type: Cooldown bucket type

1002

"""

1003

1004

def max_concurrency(number: int, *, per: BucketType = BucketType.default, wait: bool = False):

1005

"""

1006

Limit concurrent command usage.

1007

1008

Parameters:

1009

- number: Maximum concurrent uses

1010

- per: Concurrency bucket type

1011

- wait: Whether to wait for slot

1012

"""

1013

1014

class BucketType(enum.Enum):

1015

"""Cooldown bucket types."""

1016

1017

default = 0

1018

user = 1

1019

guild = 2

1020

channel = 3

1021

member = 4

1022

category = 5

1023

role = 6

1024

```

1025

1026

### Help System

1027

1028

Configurable help command system for documenting bot commands and usage.

1029

1030

```python { .api }

1031

class HelpCommand:

1032

"""Base class for help commands."""

1033

1034

def __init__(

1035

self,

1036

*,

1037

verify_checks: bool = True,

1038

command_attrs: Dict[str, Any] = None,

1039

sort_commands: bool = True,

1040

show_hidden: bool = False,

1041

dm_help: Optional[bool] = None,

1042

dm_help_threshold: Optional[int] = 1000

1043

):

1044

"""

1045

Initialize help command.

1046

1047

Parameters:

1048

- verify_checks: Check command permissions

1049

- command_attrs: Help command attributes

1050

- sort_commands: Sort commands alphabetically

1051

- show_hidden: Show hidden commands

1052

- dm_help: Send help in DMs

1053

- dm_help_threshold: Character threshold for DMing

1054

"""

1055

1056

context: Optional[Context]

1057

verify_checks: bool

1058

command_attrs: Dict[str, Any]

1059

sort_commands: bool

1060

show_hidden: bool

1061

dm_help: Optional[bool]

1062

dm_help_threshold: Optional[int]

1063

1064

def add_indented_commands(

1065

self,

1066

commands: List[Command],

1067

*,

1068

heading: str,

1069

max_size: Optional[int] = None

1070

) -> None:

1071

"""

1072

Add indented command list to paginator.

1073

1074

Parameters:

1075

- commands: Commands to add

1076

- heading: Section heading

1077

- max_size: Maximum name length

1078

"""

1079

1080

def get_opening_note(self) -> str:

1081

"""

1082

Get help opening note.

1083

1084

Returns:

1085

Opening note text

1086

"""

1087

1088

def get_ending_note(self) -> str:

1089

"""

1090

Get help ending note.

1091

1092

Returns:

1093

Ending note text

1094

"""

1095

1096

def add_bot_commands_formatting(self, commands: List[Command], heading: str) -> None:

1097

"""

1098

Add bot commands to help.

1099

1100

Parameters:

1101

- commands: Commands to format

1102

- heading: Section heading

1103

"""

1104

1105

def add_subcommand_formatting(self, command: Command) -> None:

1106

"""

1107

Add subcommand information.

1108

1109

Parameters:

1110

- command: Subcommand to format

1111

"""

1112

1113

def add_aliases_formatting(self, aliases: List[str]) -> None:

1114

"""

1115

Add command aliases.

1116

1117

Parameters:

1118

- aliases: Command aliases

1119

"""

1120

1121

def add_command_formatting(self, command: Command) -> None:

1122

"""

1123

Add single command information.

1124

1125

Parameters:

1126

- command: Command to format

1127

"""

1128

1129

def get_destination(self) -> Messageable:

1130

"""

1131

Get help destination channel.

1132

1133

Returns:

1134

Destination for help message

1135

"""

1136

1137

async def filter_commands(

1138

self,

1139

commands: Iterable[Command],

1140

*,

1141

sort: bool = False,

1142

key: Optional[Callable[[Command], Any]] = None

1143

) -> List[Command]:

1144

"""

1145

Filter commands based on checks.

1146

1147

Parameters:

1148

- commands: Commands to filter

1149

- sort: Whether to sort commands

1150

- key: Sort key function

1151

1152

Returns:

1153

Filtered command list

1154

"""

1155

1156

def get_max_size(self, commands: List[Command]) -> int:

1157

"""

1158

Get maximum command name length.

1159

1160

Parameters:

1161

- commands: Commands to check

1162

1163

Returns:

1164

Maximum name length

1165

"""

1166

1167

def get_command_signature(self, command: Command) -> str:

1168

"""

1169

Get command signature.

1170

1171

Parameters:

1172

- command: Command to get signature for

1173

1174

Returns:

1175

Command signature string

1176

"""

1177

1178

async def send_bot_help(self, mapping: Dict[Optional[Cog], List[Command]]) -> None:

1179

"""

1180

Send bot-wide help.

1181

1182

Parameters:

1183

- mapping: Cog to commands mapping

1184

"""

1185

1186

async def send_cog_help(self, cog: Cog) -> None:

1187

"""

1188

Send cog-specific help.

1189

1190

Parameters:

1191

- cog: Cog to get help for

1192

"""

1193

1194

async def send_group_help(self, group: Group) -> None:

1195

"""

1196

Send group command help.

1197

1198

Parameters:

1199

- group: Command group

1200

"""

1201

1202

async def send_command_help(self, command: Command) -> None:

1203

"""

1204

Send single command help.

1205

1206

Parameters:

1207

- command: Command to get help for

1208

"""

1209

1210

async def prepare_help_command(self, ctx: Context, command: Optional[str] = None) -> None:

1211

"""

1212

Prepare help command for execution.

1213

1214

Parameters:

1215

- ctx: Command context

1216

- command: Specific command help requested

1217

"""

1218

1219

async def command_not_found(self, string: str) -> str:

1220

"""

1221

Handle command not found.

1222

1223

Parameters:

1224

- string: Command name that wasn't found

1225

1226

Returns:

1227

Error message

1228

"""

1229

1230

async def subcommand_not_found(self, command: Command, string: str) -> str:

1231

"""

1232

Handle subcommand not found.

1233

1234

Parameters:

1235

- command: Parent command

1236

- string: Subcommand name that wasn't found

1237

1238

Returns:

1239

Error message

1240

"""

1241

1242

async def send_error_message(self, error: str) -> None:

1243

"""

1244

Send error message.

1245

1246

Parameters:

1247

- error: Error message to send

1248

"""

1249

1250

async def on_help_command_error(self, ctx: Context, error: CommandError) -> None:

1251

"""

1252

Handle help command errors.

1253

1254

Parameters:

1255

- ctx: Command context

1256

- error: Error that occurred

1257

"""

1258

1259

class DefaultHelpCommand(HelpCommand):

1260

"""Default help command implementation."""

1261

1262

def __init__(

1263

self,

1264

*,

1265

paginator: Optional[Paginator] = None,

1266

**options

1267

):

1268

"""

1269

Initialize default help command.

1270

1271

Parameters:

1272

- paginator: Custom paginator

1273

- options: Base class options

1274

"""

1275

1276

class MinimalHelpCommand(HelpCommand):

1277

"""Minimal help command implementation."""

1278

1279

def __init__(self, **options):

1280

"""Initialize minimal help command."""

1281

1282

class Paginator:

1283

"""Text paginator for long help content."""

1284

1285

def __init__(

1286

self,

1287

prefix: str = '```',

1288

suffix: str = '```',

1289

max_size: int = 2000,

1290

linesep: str = '\n'

1291

):

1292

"""

1293

Initialize paginator.

1294

1295

Parameters:

1296

- prefix: Page prefix

1297

- suffix: Page suffix

1298

- max_size: Maximum page size

1299

- linesep: Line separator

1300

"""

1301

1302

@property

1303

def pages(self) -> List[str]:

1304

"""Paginated pages."""

1305

1306

def add_line(self, line: str = '', *, empty: bool = False) -> None:

1307

"""

1308

Add line to paginator.

1309

1310

Parameters:

1311

- line: Line content

1312

- empty: Whether to add empty line

1313

"""

1314

1315

def close_page(self) -> None:

1316

"""Close current page."""

1317

```

1318

1319

## Usage Examples

1320

1321

### Basic Command Bot Setup

1322

1323

```python

1324

import disnake

1325

from disnake.ext import commands

1326

1327

# Create bot with command prefix

1328

bot = commands.Bot(

1329

command_prefix='!',

1330

description='A helpful bot',

1331

intents=disnake.Intents.all(),

1332

help_command=commands.DefaultHelpCommand(),

1333

case_insensitive=True

1334

)

1335

1336

@bot.event

1337

async def on_ready():

1338

print(f'Bot ready: {bot.user}')

1339

1340

@bot.event

1341

async def on_command_error(ctx, error):

1342

"""Global command error handler."""

1343

if isinstance(error, commands.CommandNotFound):

1344

return # Ignore unknown commands

1345

1346

elif isinstance(error, commands.MissingRequiredArgument):

1347

await ctx.send(f"Missing argument: `{error.param}`")

1348

1349

elif isinstance(error, commands.BadArgument):

1350

await ctx.send(f"Invalid argument: {error}")

1351

1352

elif isinstance(error, commands.CheckFailure):

1353

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

1354

1355

elif isinstance(error, commands.CommandOnCooldown):

1356

await ctx.send(f"Command on cooldown. Try again in {error.retry_after:.2f} seconds.")

1357

1358

else:

1359

print(f'Command error: {error}')

1360

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

1361

1362

# Basic commands

1363

@bot.command()

1364

async def ping(ctx):

1365

"""Check bot latency."""

1366

latency = round(bot.latency * 1000)

1367

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

1368

1369

@bot.command()

1370

async def echo(ctx, *, message):

1371

"""Echo a message."""

1372

await ctx.send(message)

1373

1374

@bot.command()

1375

async def userinfo(ctx, user: disnake.User = None):

1376

"""Get information about a user."""

1377

if user is None:

1378

user = ctx.author

1379

1380

embed = disnake.Embed(title=f"User Info: {user}", color=0x00ff00)

1381

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

1382

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

1383

embed.add_field(name="Created", value=f"<t:{int(user.created_at.timestamp())}:F>", inline=True)

1384

1385

if isinstance(user, disnake.Member):

1386

embed.add_field(name="Joined", value=f"<t:{int(user.joined_at.timestamp())}:F>", inline=True)

1387

embed.add_field(name="Roles", value=len(user.roles) - 1, inline=True)

1388

1389

await ctx.send(embed=embed)

1390

1391

bot.run('YOUR_BOT_TOKEN')

1392

```

1393

1394

### Command Groups and Subcommands

1395

1396

```python

1397

@bot.group(invoke_without_subcommand=True, case_insensitive=True)

1398

async def config(ctx):

1399

"""Configuration commands."""

1400

if ctx.invoked_subcommand is None:

1401

await ctx.send("Available config options: prefix, welcome, autorole")

1402

1403

@config.command()

1404

@commands.has_permissions(administrator=True)

1405

async def prefix(ctx, new_prefix: str = None):

1406

"""View or change bot prefix."""

1407

if new_prefix is None:

1408

current_prefix = await bot.get_prefix(ctx.message)

1409

await ctx.send(f"Current prefix: `{current_prefix[0]}`")

1410

else:

1411

# In a real bot, save to database

1412

bot.command_prefix = new_prefix

1413

await ctx.send(f"Prefix changed to: `{new_prefix}`")

1414

1415

@config.command()

1416

@commands.has_permissions(manage_guild=True)

1417

async def welcome(ctx, channel: disnake.TextChannel = None):

1418

"""Set welcome channel."""

1419

if channel is None:

1420

await ctx.send("Please specify a channel.")

1421

return

1422

1423

# Save to database in real implementation

1424

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

1425

1426

@bot.group()

1427

@commands.has_permissions(manage_roles=True)

1428

async def role(ctx):

1429

"""Role management commands."""

1430

if ctx.invoked_subcommand is None:

1431

await ctx.send_help(ctx.command)

1432

1433

@role.command()

1434

async def create(ctx, *, name: str):

1435

"""Create a new role."""

1436

role = await ctx.guild.create_role(name=name, reason=f"Created by {ctx.author}")

1437

await ctx.send(f"Created role: {role.mention}")

1438

1439

@role.command()

1440

async def delete(ctx, role: disnake.Role):

1441

"""Delete a role."""

1442

if role >= ctx.author.top_role:

1443

return await ctx.send("You cannot delete this role.")

1444

1445

await role.delete(reason=f"Deleted by {ctx.author}")

1446

await ctx.send(f"Deleted role: {role.name}")

1447

1448

@role.command()

1449

async def give(ctx, member: disnake.Member, role: disnake.Role):

1450

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

1451

if role >= ctx.author.top_role:

1452

return await ctx.send("You cannot assign this role.")

1453

1454

if role in member.roles:

1455

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

1456

1457

await member.add_roles(role, reason=f"Added by {ctx.author}")

1458

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

1459

1460

@role.command()

1461

async def remove(ctx, member: disnake.Member, role: disnake.Role):

1462

"""Remove a role from a member."""

1463

if role >= ctx.author.top_role:

1464

return await ctx.send("You cannot manage this role.")

1465

1466

if role not in member.roles:

1467

return await ctx.send(f"{member} doesn't have the {role.name} role.")

1468

1469

await member.remove_roles(role, reason=f"Removed by {ctx.author}")

1470

await ctx.send(f"Removed {role.name} from {member.mention}")

1471

```

1472

1473

### Advanced Parameter Handling

1474

1475

```python

1476

from typing import Union, Optional, Literal

1477

1478

@bot.command()

1479

async def kick(

1480

ctx,

1481

member: disnake.Member,

1482

*,

1483

reason: Optional[str] = "No reason provided"

1484

):

1485

"""Kick a member from the server."""

1486

if not ctx.author.guild_permissions.kick_members:

1487

return await ctx.send("You don't have permission to kick members.")

1488

1489

if member.top_role >= ctx.author.top_role:

1490

return await ctx.send("You cannot kick this member.")

1491

1492

await member.kick(reason=reason)

1493

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

1494

1495

@bot.command()

1496

async def ban(

1497

ctx,

1498

user: Union[disnake.Member, disnake.User],

1499

delete_days: Optional[int] = 1,

1500

*,

1501

reason: Optional[str] = "No reason provided"

1502

):

1503

"""Ban a user from the server."""

1504

if not ctx.author.guild_permissions.ban_members:

1505

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

1506

1507

if isinstance(user, disnake.Member) and user.top_role >= ctx.author.top_role:

1508

return await ctx.send("You cannot ban this member.")

1509

1510

if not 0 <= delete_days <= 7:

1511

return await ctx.send("Delete days must be between 0 and 7.")

1512

1513

await ctx.guild.ban(user, delete_message_days=delete_days, reason=reason)

1514

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

1515

1516

@bot.command()

1517

async def purge(

1518

ctx,

1519

limit: int = 10,

1520

target: Union[disnake.Member, disnake.User, str, None] = None

1521

):

1522

"""Purge messages from channel."""

1523

if not ctx.author.guild_permissions.manage_messages:

1524

return await ctx.send("You don't have permission to purge messages.")

1525

1526

if limit > 100:

1527

return await ctx.send("Cannot purge more than 100 messages at once.")

1528

1529

def message_check(message):

1530

if target is None:

1531

return True

1532

elif isinstance(target, (disnake.Member, disnake.User)):

1533

return message.author == target

1534

elif isinstance(target, str):

1535

return target.lower() in message.content.lower()

1536

return False

1537

1538

deleted = await ctx.channel.purge(limit=limit, check=message_check)

1539

await ctx.send(f"Purged {len(deleted)} messages.", delete_after=5)

1540

1541

@bot.command()

1542

async def remind(

1543

ctx,

1544

time: str,

1545

*,

1546

reminder: str

1547

):

1548

"""Set a reminder (e.g., !remind 1h Take break)."""

1549

import re

1550

from datetime import datetime, timedelta

1551

1552

# Parse time string

1553

time_regex = re.compile(r'(\d+)([smhd])')

1554

matches = time_regex.findall(time.lower())

1555

1556

if not matches:

1557

return await ctx.send("Invalid time format. Use: 1s, 1m, 1h, 1d")

1558

1559

total_seconds = 0

1560

for amount, unit in matches:

1561

amount = int(amount)

1562

if unit == 's':

1563

total_seconds += amount

1564

elif unit == 'm':

1565

total_seconds += amount * 60

1566

elif unit == 'h':

1567

total_seconds += amount * 3600

1568

elif unit == 'd':

1569

total_seconds += amount * 86400

1570

1571

if total_seconds > 86400 * 30: # 30 days max

1572

return await ctx.send("Reminder cannot be longer than 30 days.")

1573

1574

# Schedule reminder (in real bot, use a task scheduler)

1575

reminder_time = datetime.utcnow() + timedelta(seconds=total_seconds)

1576

1577

await ctx.send(f"Reminder set for <t:{int(reminder_time.timestamp())}:F>")

1578

1579

# Simple sleep for demo (use proper task scheduling in production)

1580

await asyncio.sleep(total_seconds)

1581

1582

try:

1583

await ctx.author.send(f"⏰ Reminder: {reminder}")

1584

except disnake.Forbidden:

1585

await ctx.send(f"⏰ {ctx.author.mention}, reminder: {reminder}")

1586

1587

# Custom converter example

1588

class ColorConverter(commands.Converter):

1589

async def convert(self, ctx, argument):

1590

# Try hex color

1591

if argument.startswith('#'):

1592

try:

1593

return disnake.Color(int(argument[1:], 16))

1594

except ValueError:

1595

pass

1596

1597

# Try color name

1598

try:

1599

return getattr(disnake.Color, argument.lower())()

1600

except AttributeError:

1601

pass

1602

1603

# Try RGB values

1604

if ',' in argument:

1605

try:

1606

r, g, b = map(int, argument.split(','))

1607

return disnake.Color.from_rgb(r, g, b)

1608

except ValueError:

1609

pass

1610

1611

raise commands.BadArgument(f"Could not convert '{argument}' to a color.")

1612

1613

@bot.command()

1614

async def color(ctx, color: ColorConverter):

1615

"""Test color conversion."""

1616

embed = disnake.Embed(title="Color Test", color=color)

1617

embed.add_field(name="RGB", value=f"{color.r}, {color.g}, {color.b}")

1618

embed.add_field(name="Hex", value=f"#{color.value:06x}")

1619

await ctx.send(embed=embed)

1620

1621

# Flag system example

1622

class ModFlags(commands.FlagConverter):

1623

silent: bool = commands.flag(default=False, description="Don't send notification")

1624

reason: Optional[str] = commands.flag(default=None, description="Reason for action")

1625

duration: Optional[str] = commands.flag(default=None, description="Duration for temporary actions")

1626

1627

@bot.command()

1628

async def tempban(ctx, member: disnake.Member, **flags: ModFlags):

1629

"""Temporarily ban a member with flags."""

1630

if not ctx.author.guild_permissions.ban_members:

1631

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

1632

1633

duration = flags.duration or "1d"

1634

reason = flags.reason or "No reason provided"

1635

silent = flags.silent

1636

1637

# Parse duration and implement temp ban logic

1638

await ctx.send(f"Temp banned {member} for {duration}. Reason: {reason}. Silent: {silent}")

1639

```

1640

1641

### Cog System Implementation

1642

1643

```python

1644

# cogs/moderation.py

1645

class Moderation(commands.Cog):

1646

"""Moderation commands and features."""

1647

1648

def __init__(self, bot):

1649

self.bot = bot

1650

self.muted_members = set() # In production, use database

1651

1652

async def cog_check(self, ctx):

1653

"""Check if user has moderation permissions."""

1654

return ctx.author.guild_permissions.manage_messages

1655

1656

async def cog_command_error(self, ctx, error):

1657

"""Handle cog command errors."""

1658

if isinstance(error, commands.CheckFailure):

1659

await ctx.send("You need moderation permissions to use this command.")

1660

else:

1661

# Re-raise for global handler

1662

raise error

1663

1664

@commands.Cog.listener()

1665

async def on_message(self, message):

1666

"""Monitor messages for auto-moderation."""

1667

if message.author.bot or not message.guild:

1668

return

1669

1670

# Auto-delete messages with too many mentions

1671

if len(message.mentions) > 5:

1672

await message.delete()

1673

await message.channel.send(

1674

f"{message.author.mention}, please don't spam mentions.",

1675

delete_after=5

1676

)

1677

1678

@commands.Cog.listener()

1679

async def on_member_join(self, member):

1680

"""Auto-mute members if they were previously muted."""

1681

if member.id in self.muted_members:

1682

mute_role = disnake.utils.get(member.guild.roles, name="Muted")

1683

if mute_role:

1684

await member.add_roles(mute_role)

1685

1686

@commands.command()

1687

async def warn(self, ctx, member: disnake.Member, *, reason):

1688

"""Warn a member."""

1689

# In production, save to database

1690

embed = disnake.Embed(

1691

title="Member Warned",

1692

description=f"{member.mention} has been warned.",

1693

color=0xffaa00

1694

)

1695

embed.add_field(name="Reason", value=reason)

1696

embed.add_field(name="Moderator", value=ctx.author.mention)

1697

1698

await ctx.send(embed=embed)

1699

1700

try:

1701

await member.send(f"You have been warned in {ctx.guild.name}: {reason}")

1702

except disnake.Forbidden:

1703

pass

1704

1705

@commands.command()

1706

async def mute(self, ctx, member: disnake.Member, duration: str = "10m", *, reason="No reason"):

1707

"""Mute a member."""

1708

mute_role = disnake.utils.get(ctx.guild.roles, name="Muted")

1709

1710

if not mute_role:

1711

# Create mute role

1712

mute_role = await ctx.guild.create_role(

1713

name="Muted",

1714

permissions=disnake.Permissions(send_messages=False, speak=False)

1715

)

1716

1717

# Set permissions for all channels

1718

for channel in ctx.guild.channels:

1719

await channel.set_permissions(

1720

mute_role,

1721

send_messages=False,

1722

speak=False

1723

)

1724

1725

await member.add_roles(mute_role, reason=reason)

1726

self.muted_members.add(member.id)

1727

1728

await ctx.send(f"Muted {member} for {duration}. Reason: {reason}")

1729

1730

# Schedule unmute (simplified - use proper task scheduler)

1731

# parse_duration(duration) would return seconds

1732

# await asyncio.sleep(seconds)

1733

# await member.remove_roles(mute_role)

1734

1735

@commands.command()

1736

async def unmute(self, ctx, member: disnake.Member):

1737

"""Unmute a member."""

1738

mute_role = disnake.utils.get(ctx.guild.roles, name="Muted")

1739

1740

if not mute_role or mute_role not in member.roles:

1741

return await ctx.send("Member is not muted.")

1742

1743

await member.remove_roles(mute_role)

1744

self.muted_members.discard(member.id)

1745

await ctx.send(f"Unmuted {member}")

1746

1747

@commands.group()

1748

async def automod(self, ctx):

1749

"""Auto-moderation settings."""

1750

if ctx.invoked_subcommand is None:

1751

await ctx.send_help(ctx.command)

1752

1753

@automod.command()

1754

async def spam(self, ctx, enabled: bool):

1755

"""Toggle spam protection."""

1756

# Save setting to database

1757

await ctx.send(f"Spam protection {'enabled' if enabled else 'disabled'}.")

1758

1759

# cogs/fun.py

1760

class Fun(commands.Cog):

1761

"""Fun and entertainment commands."""

1762

1763

def __init__(self, bot):

1764

self.bot = bot

1765

1766

@commands.command()

1767

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

1768

async def roll(self, ctx, dice: str = "1d6"):

1769

"""Roll dice (e.g., 1d6, 2d20)."""

1770

import random

1771

import re

1772

1773

match = re.match(r'(\d+)d(\d+)', dice)

1774

if not match:

1775

return await ctx.send("Invalid dice format. Use format like: 1d6, 2d20")

1776

1777

count, sides = map(int, match.groups())

1778

1779

if count > 10 or sides > 100:

1780

return await ctx.send("Too many dice or sides!")

1781

1782

rolls = [random.randint(1, sides) for _ in range(count)]

1783

result = sum(rolls)

1784

1785

embed = disnake.Embed(title=f"🎲 Dice Roll: {dice}")

1786

embed.add_field(name="Rolls", value=" + ".join(map(str, rolls)))

1787

embed.add_field(name="Total", value=result)

1788

1789

await ctx.send(embed=embed)

1790

1791

@commands.command()

1792

async def choose(self, ctx, *choices):

1793

"""Choose randomly from options."""

1794

if len(choices) < 2:

1795

return await ctx.send("Please provide at least 2 choices.")

1796

1797

choice = random.choice(choices)

1798

await ctx.send(f"I choose: **{choice}**")

1799

1800

@commands.command()

1801

async def coinflip(self, ctx):

1802

"""Flip a coin."""

1803

result = random.choice(["Heads", "Tails"])

1804

emoji = "🟡" if result == "Heads" else "⚫"

1805

await ctx.send(f"{emoji} **{result}**!")

1806

1807

# cogs/utility.py

1808

class Utility(commands.Cog):

1809

"""Utility commands."""

1810

1811

def __init__(self, bot):

1812

self.bot = bot

1813

1814

@commands.command()

1815

async def serverinfo(self, ctx):

1816

"""Show server information."""

1817

guild = ctx.guild

1818

1819

embed = disnake.Embed(title=guild.name, color=0x00ff00)

1820

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

1821

1822

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

1823

embed.add_field(name="Created", value=f"<t:{int(guild.created_at.timestamp())}:F>")

1824

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

1825

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

1826

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

1827

embed.add_field(name="Boost Level", value=guild.premium_tier)

1828

1829

await ctx.send(embed=embed)

1830

1831

@commands.command()

1832

async def avatar(self, ctx, user: disnake.User = None):

1833

"""Show user avatar."""

1834

user = user or ctx.author

1835

1836

embed = disnake.Embed(title=f"{user}'s Avatar")

1837

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

1838

await ctx.send(embed=embed)

1839

1840

@commands.command()

1841

async def poll(self, ctx, question, *options):

1842

"""Create a poll with reactions."""

1843

if len(options) > 10:

1844

return await ctx.send("Too many options! Maximum 10.")

1845

1846

if len(options) < 2:

1847

return await ctx.send("Need at least 2 options!")

1848

1849

reactions = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟']

1850

1851

embed = disnake.Embed(title=f"📊 {question}", color=0x0099ff)

1852

1853

for i, option in enumerate(options):

1854

embed.add_field(

1855

name=f"{reactions[i]} Option {i+1}",

1856

value=option,

1857

inline=False

1858

)

1859

1860

message = await ctx.send(embed=embed)

1861

1862

for i in range(len(options)):

1863

await message.add_reaction(reactions[i])

1864

1865

# Load cogs

1866

async def setup(bot):

1867

await bot.add_cog(Moderation(bot))

1868

await bot.add_cog(Fun(bot))

1869

await bot.add_cog(Utility(bot))

1870

1871

# In main bot file

1872

async def load_extensions():

1873

initial_extensions = [

1874

'cogs.moderation',

1875

'cogs.fun',

1876

'cogs.utility'

1877

]

1878

1879

for extension in initial_extensions:

1880

try:

1881

await bot.load_extension(extension)

1882

print(f'Loaded {extension}')

1883

except Exception as e:

1884

print(f'Failed to load {extension}: {e}')

1885

1886

# Extension management commands

1887

@bot.command()

1888

@commands.is_owner()

1889

async def load(ctx, extension):

1890

"""Load an extension."""

1891

try:

1892

await bot.load_extension(f'cogs.{extension}')

1893

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

1894

except Exception as e:

1895

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

1896

1897

@bot.command()

1898

@commands.is_owner()

1899

async def unload(ctx, extension):

1900

"""Unload an extension."""

1901

try:

1902

await bot.unload_extension(f'cogs.{extension}')

1903

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

1904

except Exception as e:

1905

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

1906

1907

@bot.command()

1908

@commands.is_owner()

1909

async def reload(ctx, extension):

1910

"""Reload an extension."""

1911

try:

1912

await bot.reload_extension(f'cogs.{extension}')

1913

await ctx.send(f'🔄 Reloaded {extension}')

1914

except Exception as e:

1915

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

1916

1917

# Setup hook

1918

async def main():

1919

await load_extensions()

1920

await bot.start('YOUR_BOT_TOKEN')

1921

1922

if __name__ == '__main__':

1923

asyncio.run(main())

1924

```