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

events.mddocs/

0

# Nextcord Events

1

2

Event handling system for Discord gateway events and raw event data for advanced use cases with comprehensive event monitoring capabilities.

3

4

## Event System

5

6

Core event handling mechanism for responding to Discord gateway events.

7

8

### Event Decorator and Registration { .api }

9

10

```python

11

import nextcord

12

from nextcord.ext import commands

13

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

14

from datetime import datetime

15

16

# Event registration using decorator

17

@bot.event

18

async def on_ready():

19

"""Called when the client has successfully connected to Discord.

20

21

This event is called when the bot has finished logging in and setting up.

22

It's only called once per login session.

23

"""

24

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

25

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

26

print(f'Connected at: {datetime.now()}')

27

28

# Set bot status

29

activity = nextcord.Activity(

30

type=nextcord.ActivityType.watching,

31

name=f"{len(bot.guilds)} servers"

32

)

33

await bot.change_presence(status=nextcord.Status.online, activity=activity)

34

35

# Manual event registration

36

async def my_event_handler():

37

"""Custom event handler function."""

38

print("Custom event triggered!")

39

40

# Register event manually

41

bot.add_listener(my_event_handler, 'on_ready')

42

43

# Multiple listeners for the same event

44

@bot.event

45

async def on_ready():

46

"""First ready handler."""

47

print("First ready handler called")

48

49

@bot.listen('on_ready') # Use listen() to add additional handlers

50

async def second_ready_handler():

51

"""Second ready handler."""

52

print("Second ready handler called")

53

54

# Event with parameters

55

@bot.event

56

async def on_message(message: nextcord.Message):

57

"""Called when a message is sent in a channel the bot can see.

58

59

Parameters

60

----------

61

message: nextcord.Message

62

The message that was sent.

63

"""

64

# Ignore messages from bots

65

if message.author.bot:

66

return

67

68

print(f'{message.author}: {message.content}')

69

70

# Process commands (required for command framework)

71

await bot.process_commands(message)

72

73

# Event error handling

74

@bot.event

75

async def on_error(event: str, *args, **kwargs):

76

"""Called when an event raises an uncaught exception.

77

78

Parameters

79

----------

80

event: str

81

The name of the event that raised the exception.

82

*args

83

The positional arguments passed to the event.

84

**kwargs

85

The keyword arguments passed to the event.

86

"""

87

import traceback

88

89

print(f'An error occurred in {event}:')

90

traceback.print_exc()

91

92

# Log error to file or database

93

with open('error_log.txt', 'a') as f:

94

f.write(f'{datetime.now()}: Error in {event}\n')

95

traceback.print_exc(file=f)

96

f.write('\n')

97

```

98

99

## Connection Events

100

101

Events related to bot connection and disconnection states.

102

103

### Connection State Events { .api }

104

105

```python

106

@bot.event

107

async def on_connect():

108

"""Called when the client has successfully connected to Discord.

109

110

This is different from on_ready in that it's called every time

111

the websocket connection is established (including reconnections).

112

"""

113

print(f'Connected to Discord at {datetime.now()}')

114

115

@bot.event

116

async def on_disconnect():

117

"""Called when the client has disconnected from Discord.

118

119

This could be due to internet issues, Discord issues, or

120

the bot being shut down.

121

"""

122

print(f'Disconnected from Discord at {datetime.now()}')

123

124

@bot.event

125

async def on_resumed():

126

"""Called when the client has resumed a session.

127

128

This is called when the websocket connection has been resumed

129

after a disconnect, rather than creating a new session.

130

"""

131

print(f'Resumed Discord connection at {datetime.now()}')

132

133

# Advanced connection monitoring

134

class ConnectionMonitor:

135

"""Monitor connection events and track statistics."""

136

137

def __init__(self, bot):

138

self.bot = bot

139

self.connection_count = 0

140

self.disconnect_count = 0

141

self.resume_count = 0

142

self.first_connect_time = None

143

self.last_disconnect_time = None

144

self.uptime_start = None

145

146

@commands.Cog.listener()

147

async def on_connect(self):

148

"""Track connection events."""

149

self.connection_count += 1

150

current_time = datetime.now()

151

152

if self.first_connect_time is None:

153

self.first_connect_time = current_time

154

self.uptime_start = current_time

155

156

print(f'Connection #{self.connection_count} established')

157

158

@commands.Cog.listener()

159

async def on_disconnect(self):

160

"""Track disconnection events."""

161

self.disconnect_count += 1

162

self.last_disconnect_time = datetime.now()

163

164

print(f'Disconnect #{self.disconnect_count} occurred')

165

166

@commands.Cog.listener()

167

async def on_resumed(self):

168

"""Track resume events."""

169

self.resume_count += 1

170

print(f'Resume #{self.resume_count} occurred')

171

172

@commands.Cog.listener()

173

async def on_ready(self):

174

"""Log ready state with connection info."""

175

current_time = datetime.now()

176

177

if self.uptime_start:

178

uptime = current_time - self.uptime_start

179

print(f'Bot ready! Uptime: {uptime}')

180

181

print(f'Connection stats: {self.connection_count} connects, '

182

f'{self.disconnect_count} disconnects, {self.resume_count} resumes')

183

184

# Add the connection monitor

185

bot.add_cog(ConnectionMonitor(bot))

186

187

# Sharded connection events (for AutoShardedBot)

188

@bot.event

189

async def on_shard_connect(shard_id: int):

190

"""Called when a shard connects.

191

192

Parameters

193

----------

194

shard_id: int

195

The ID of the shard that connected.

196

"""

197

print(f'Shard {shard_id} connected')

198

199

@bot.event

200

async def on_shard_disconnect(shard_id: int):

201

"""Called when a shard disconnects.

202

203

Parameters

204

----------

205

shard_id: int

206

The ID of the shard that disconnected.

207

"""

208

print(f'Shard {shard_id} disconnected')

209

210

@bot.event

211

async def on_shard_ready(shard_id: int):

212

"""Called when a shard becomes ready.

213

214

Parameters

215

----------

216

shard_id: int

217

The ID of the shard that became ready.

218

"""

219

print(f'Shard {shard_id} is ready')

220

221

@bot.event

222

async def on_shard_resumed(shard_id: int):

223

"""Called when a shard resumes.

224

225

Parameters

226

----------

227

shard_id: int

228

The ID of the shard that resumed.

229

"""

230

print(f'Shard {shard_id} resumed')

231

```

232

233

## Message Events

234

235

Events related to message creation, editing, and deletion.

236

237

### Message Lifecycle Events { .api }

238

239

```python

240

@bot.event

241

async def on_message(message: nextcord.Message):

242

"""Called when a message is created.

243

244

Parameters

245

----------

246

message: nextcord.Message

247

The message that was created.

248

"""

249

# Skip bot messages

250

if message.author.bot:

251

return

252

253

# Log message statistics

254

print(f'Message from {message.author} in {message.channel}: {len(message.content)} chars')

255

256

# Auto-moderation example

257

if any(word in message.content.lower() for word in ['spam', 'advertisement']):

258

await message.delete()

259

await message.channel.send(

260

f"{message.author.mention}, that message was removed for spam.",

261

delete_after=5

262

)

263

return

264

265

# Process commands

266

await bot.process_commands(message)

267

268

@bot.event

269

async def on_message_edit(before: nextcord.Message, after: nextcord.Message):

270

"""Called when a message is edited.

271

272

Parameters

273

----------

274

before: nextcord.Message

275

The message before editing.

276

after: nextcord.Message

277

The message after editing.

278

"""

279

# Skip bot messages

280

if before.author.bot:

281

return

282

283

# Skip if content didn't change (embed updates, etc.)

284

if before.content == after.content:

285

return

286

287

# Log edit to moderation channel

288

mod_channel = nextcord.utils.get(after.guild.channels, name='mod-logs')

289

if mod_channel:

290

embed = nextcord.Embed(

291

title="๐Ÿ“ Message Edited",

292

color=nextcord.Color.orange(),

293

timestamp=datetime.now()

294

)

295

296

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

297

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

298

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

299

300

# Show before/after content (truncated)

301

before_content = before.content[:1000] + "..." if len(before.content) > 1000 else before.content

302

after_content = after.content[:1000] + "..." if len(after.content) > 1000 else after.content

303

304

embed.add_field(name="Before", value=before_content or "*No content*", inline=False)

305

embed.add_field(name="After", value=after_content or "*No content*", inline=False)

306

307

embed.add_field(name="Jump to Message", value=f"[Click here]({after.jump_url})", inline=False)

308

309

await mod_channel.send(embed=embed)

310

311

@bot.event

312

async def on_message_delete(message: nextcord.Message):

313

"""Called when a message is deleted.

314

315

Parameters

316

----------

317

message: nextcord.Message

318

The message that was deleted.

319

"""

320

# Skip bot messages

321

if message.author.bot:

322

return

323

324

# Log deletion

325

mod_channel = nextcord.utils.get(message.guild.channels, name='mod-logs')

326

if mod_channel:

327

embed = nextcord.Embed(

328

title="๐Ÿ—‘๏ธ Message Deleted",

329

color=nextcord.Color.red(),

330

timestamp=datetime.now()

331

)

332

333

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

334

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

335

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

336

337

# Show deleted content (truncated)

338

content = message.content[:1500] + "..." if len(message.content) > 1500 else message.content

339

embed.add_field(name="Content", value=content or "*No text content*", inline=False)

340

341

# Show attachments if any

342

if message.attachments:

343

attachment_info = []

344

for attachment in message.attachments[:5]: # Show first 5

345

attachment_info.append(f"โ€ข {attachment.filename} ({attachment.size} bytes)")

346

347

embed.add_field(

348

name=f"Attachments ({len(message.attachments)})",

349

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

350

inline=False

351

)

352

353

embed.set_footer(text=f"Originally sent", icon_url=message.author.display_avatar.url)

354

embed.timestamp = message.created_at

355

356

await mod_channel.send(embed=embed)

357

358

# Bulk message operations

359

@bot.event

360

async def on_bulk_message_delete(messages: List[nextcord.Message]):

361

"""Called when messages are bulk deleted.

362

363

Parameters

364

----------

365

messages: List[nextcord.Message]

366

The messages that were deleted.

367

"""

368

if not messages:

369

return

370

371

# Get guild and channel from first message

372

first_message = messages[0]

373

guild = first_message.guild

374

channel = first_message.channel

375

376

# Log bulk deletion

377

mod_channel = nextcord.utils.get(guild.channels, name='mod-logs')

378

if mod_channel:

379

embed = nextcord.Embed(

380

title="๐Ÿ—‘๏ธ Bulk Message Deletion",

381

description=f"{len(messages)} messages deleted from {channel.mention}",

382

color=nextcord.Color.red(),

383

timestamp=datetime.now()

384

)

385

386

# Show message count by author

387

author_counts = {}

388

for message in messages:

389

author_counts[message.author] = author_counts.get(message.author, 0) + 1

390

391

# Top 5 authors

392

top_authors = sorted(author_counts.items(), key=lambda x: x[1], reverse=True)[:5]

393

author_text = []

394

for author, count in top_authors:

395

author_text.append(f"โ€ข {author.mention}: {count} messages")

396

397

embed.add_field(name="Messages by Author", value="\n".join(author_text), inline=False)

398

399

# Time range

400

if len(messages) > 1:

401

oldest = min(messages, key=lambda m: m.created_at)

402

newest = max(messages, key=lambda m: m.created_at)

403

404

embed.add_field(

405

name="Time Range",

406

value=f"From {oldest.created_at.strftime('%Y-%m-%d %H:%M')} to {newest.created_at.strftime('%Y-%m-%d %H:%M')}",

407

inline=False

408

)

409

410

await mod_channel.send(embed=embed)

411

412

# Message reaction events

413

@bot.event

414

async def on_reaction_add(reaction: nextcord.Reaction, user: nextcord.User):

415

"""Called when a reaction is added to a message.

416

417

Parameters

418

----------

419

reaction: nextcord.Reaction

420

The reaction that was added.

421

user: nextcord.User

422

The user who added the reaction.

423

"""

424

# Skip bot reactions

425

if user.bot:

426

return

427

428

message = reaction.message

429

430

# Role reaction system example

431

if message.id == ROLE_MESSAGE_ID: # Configure this

432

role_mapping = {

433

'๐ŸŽฎ': 'Gamer',

434

'๐ŸŽต': 'Music Lover',

435

'๐Ÿ“š': 'Bookworm',

436

'๐ŸŽจ': 'Artist'

437

}

438

439

role_name = role_mapping.get(str(reaction.emoji))

440

if role_name:

441

role = nextcord.utils.get(message.guild.roles, name=role_name)

442

if role:

443

member = message.guild.get_member(user.id)

444

if member:

445

await member.add_roles(role, reason="Role reaction")

446

print(f"Added {role.name} to {member}")

447

448

@bot.event

449

async def on_reaction_remove(reaction: nextcord.Reaction, user: nextcord.User):

450

"""Called when a reaction is removed from a message.

451

452

Parameters

453

----------

454

reaction: nextcord.Reaction

455

The reaction that was removed.

456

user: nextcord.User

457

The user who removed the reaction.

458

"""

459

# Skip bot reactions

460

if user.bot:

461

return

462

463

message = reaction.message

464

465

# Remove role when reaction is removed

466

if message.id == ROLE_MESSAGE_ID: # Configure this

467

role_mapping = {

468

'๐ŸŽฎ': 'Gamer',

469

'๐ŸŽต': 'Music Lover',

470

'๐Ÿ“š': 'Bookworm',

471

'๐ŸŽจ': 'Artist'

472

}

473

474

role_name = role_mapping.get(str(reaction.emoji))

475

if role_name:

476

role = nextcord.utils.get(message.guild.roles, name=role_name)

477

if role:

478

member = message.guild.get_member(user.id)

479

if member:

480

await member.remove_roles(role, reason="Role reaction removed")

481

print(f"Removed {role.name} from {member}")

482

483

@bot.event

484

async def on_reaction_clear(message: nextcord.Message, reactions: List[nextcord.Reaction]):

485

"""Called when all reactions are cleared from a message.

486

487

Parameters

488

----------

489

message: nextcord.Message

490

The message that had reactions cleared.

491

reactions: List[nextcord.Reaction]

492

The reactions that were cleared.

493

"""

494

print(f"All reactions cleared from message {message.id} in {message.channel}")

495

```

496

497

## Member and Guild Events

498

499

Events related to guild membership and server changes.

500

501

### Member Events { .api }

502

503

```python

504

@bot.event

505

async def on_member_join(member: nextcord.Member):

506

"""Called when a member joins a guild.

507

508

Parameters

509

----------

510

member: nextcord.Member

511

The member who joined.

512

"""

513

guild = member.guild

514

515

# Send welcome message

516

welcome_channel = nextcord.utils.get(guild.channels, name='welcome')

517

if welcome_channel:

518

embed = nextcord.Embed(

519

title="๐Ÿ‘‹ Welcome!",

520

description=f"Welcome to **{guild.name}**, {member.mention}!",

521

color=nextcord.Color.green(),

522

timestamp=datetime.now()

523

)

524

525

embed.add_field(name="Member Count", value=f"You are member #{guild.member_count}", inline=True)

526

embed.add_field(name="Account Created", value=member.created_at.strftime("%B %d, %Y"), inline=True)

527

528

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

529

embed.set_footer(text=f"ID: {member.id}")

530

531

await welcome_channel.send(embed=embed)

532

533

# Auto-role assignment

534

auto_role = nextcord.utils.get(guild.roles, name='Member')

535

if auto_role:

536

try:

537

await member.add_roles(auto_role, reason="Auto-role on join")

538

print(f"Gave {auto_role.name} to {member}")

539

except nextcord.Forbidden:

540

print(f"Failed to give auto-role to {member} - insufficient permissions")

541

542

# Log join

543

log_channel = nextcord.utils.get(guild.channels, name='member-logs')

544

if log_channel:

545

embed = nextcord.Embed(

546

title="๐Ÿ“ˆ Member Joined",

547

color=nextcord.Color.green(),

548

timestamp=datetime.now()

549

)

550

551

embed.add_field(name="Member", value=f"{member} ({member.mention})", inline=True)

552

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

553

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

554

555

# Account age

556

account_age = datetime.now() - member.created_at

557

embed.add_field(name="Account Age", value=f"{account_age.days} days", inline=True)

558

559

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

560

561

await log_channel.send(embed=embed)

562

563

@bot.event

564

async def on_member_remove(member: nextcord.Member):

565

"""Called when a member leaves a guild.

566

567

Parameters

568

----------

569

member: nextcord.Member

570

The member who left.

571

"""

572

guild = member.guild

573

574

# Send farewell message

575

farewell_channel = nextcord.utils.get(guild.channels, name='farewell')

576

if farewell_channel:

577

embed = nextcord.Embed(

578

title="๐Ÿ‘‹ Goodbye!",

579

description=f"**{member.display_name}** has left the server.",

580

color=nextcord.Color.red(),

581

timestamp=datetime.now()

582

)

583

584

embed.add_field(name="Member Count", value=f"We now have {guild.member_count} members", inline=True)

585

586

# Calculate how long they were in the server

587

if member.joined_at:

588

time_in_server = datetime.now() - member.joined_at.replace(tzinfo=None)

589

embed.add_field(name="Time in Server", value=f"{time_in_server.days} days", inline=True)

590

591

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

592

593

await farewell_channel.send(embed=embed)

594

595

# Log leave

596

log_channel = nextcord.utils.get(guild.channels, name='member-logs')

597

if log_channel:

598

embed = nextcord.Embed(

599

title="๐Ÿ“‰ Member Left",

600

color=nextcord.Color.red(),

601

timestamp=datetime.now()

602

)

603

604

embed.add_field(name="Member", value=f"{member} ({member.mention})", inline=True)

605

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

606

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

607

608

# Show roles they had

609

if member.roles[1:]: # Skip @everyone

610

roles = [role.name for role in member.roles[1:][:10]] # First 10 roles

611

embed.add_field(name="Roles", value=", ".join(roles), inline=False)

612

613

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

614

615

await log_channel.send(embed=embed)

616

617

@bot.event

618

async def on_member_update(before: nextcord.Member, after: nextcord.Member):

619

"""Called when a member updates their profile or guild-specific settings.

620

621

Parameters

622

----------

623

before: nextcord.Member

624

The member before the update.

625

after: nextcord.Member

626

The member after the update.

627

"""

628

# Skip if no relevant changes

629

if (before.display_name == after.display_name and

630

before.roles == after.roles and

631

before.status == after.status):

632

return

633

634

log_channel = nextcord.utils.get(after.guild.channels, name='member-logs')

635

if not log_channel:

636

return

637

638

embed = nextcord.Embed(

639

title="๐Ÿ‘ค Member Updated",

640

color=nextcord.Color.blue(),

641

timestamp=datetime.now()

642

)

643

644

embed.add_field(name="Member", value=after.mention, inline=True)

645

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

646

647

# Check for nickname changes

648

if before.display_name != after.display_name:

649

embed.add_field(

650

name="Nickname Change",

651

value=f"**Before:** {before.display_name}\n**After:** {after.display_name}",

652

inline=False

653

)

654

655

# Check for role changes

656

if before.roles != after.roles:

657

added_roles = [role for role in after.roles if role not in before.roles]

658

removed_roles = [role for role in before.roles if role not in after.roles]

659

660

if added_roles:

661

embed.add_field(

662

name="Roles Added",

663

value=", ".join([role.mention for role in added_roles]),

664

inline=False

665

)

666

667

if removed_roles:

668

embed.add_field(

669

name="Roles Removed",

670

value=", ".join([role.mention for role in removed_roles]),

671

inline=False

672

)

673

674

# Only send if there are actual changes to log

675

if len(embed.fields) > 2: # More than just member and ID fields

676

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

677

await log_channel.send(embed=embed)

678

679

# Guild events

680

@bot.event

681

async def on_guild_join(guild: nextcord.Guild):

682

"""Called when the bot joins a guild.

683

684

Parameters

685

----------

686

guild: nextcord.Guild

687

The guild that was joined.

688

"""

689

print(f"Joined guild: {guild.name} (ID: {guild.id})")

690

print(f"Guild has {guild.member_count} members")

691

692

# Send a greeting message to the system channel

693

if guild.system_channel:

694

embed = nextcord.Embed(

695

title="๐Ÿ‘‹ Hello!",

696

description="Thank you for adding me to your server!",

697

color=nextcord.Color.green()

698

)

699

700

embed.add_field(

701

name="Getting Started",

702

value="Use `/help` to see available commands.",

703

inline=False

704

)

705

706

embed.add_field(

707

name="Support",

708

value="Need help? Join our support server!",

709

inline=False

710

)

711

712

try:

713

await guild.system_channel.send(embed=embed)

714

except nextcord.Forbidden:

715

print(f"Cannot send greeting to {guild.name} - no permission")

716

717

@bot.event

718

async def on_guild_remove(guild: nextcord.Guild):

719

"""Called when the bot leaves a guild.

720

721

Parameters

722

----------

723

guild: nextcord.Guild

724

The guild that was left.

725

"""

726

print(f"Left guild: {guild.name} (ID: {guild.id})")

727

728

# Clean up any guild-specific data

729

# This is where you'd remove database entries, etc.

730

await cleanup_guild_data(guild.id)

731

732

@bot.event

733

async def on_guild_update(before: nextcord.Guild, after: nextcord.Guild):

734

"""Called when a guild updates.

735

736

Parameters

737

----------

738

before: nextcord.Guild

739

The guild before the update.

740

after: nextcord.Guild

741

The guild after the update.

742

"""

743

changes = []

744

745

if before.name != after.name:

746

changes.append(f"Name: {before.name} โ†’ {after.name}")

747

748

if before.description != after.description:

749

changes.append(f"Description changed")

750

751

if before.icon != after.icon:

752

changes.append(f"Icon changed")

753

754

if before.banner != after.banner:

755

changes.append(f"Banner changed")

756

757

if before.verification_level != after.verification_level:

758

changes.append(f"Verification level: {before.verification_level} โ†’ {after.verification_level}")

759

760

if changes:

761

log_channel = nextcord.utils.get(after.channels, name='server-logs')

762

if log_channel:

763

embed = nextcord.Embed(

764

title="๐Ÿ  Server Updated",

765

description="\n".join(changes),

766

color=nextcord.Color.blue(),

767

timestamp=datetime.now()

768

)

769

770

await log_channel.send(embed=embed)

771

```

772

773

## Raw Events

774

775

Raw event data for advanced use cases requiring detailed event information.

776

777

### Raw Event Handling { .api }

778

779

```python

780

@bot.event

781

async def on_raw_message_delete(payload: nextcord.RawMessageDeleteEvent):

782

"""Called when a message is deleted.

783

784

This is called even if the message is not in the bot's cache.

785

786

Parameters

787

----------

788

payload: nextcord.RawMessageDeleteEvent

789

The raw event payload.

790

"""

791

# Get the channel

792

channel = bot.get_channel(payload.channel_id)

793

if not channel:

794

return

795

796

# Check if we have the cached message

797

if payload.cached_message:

798

message = payload.cached_message

799

print(f"Cached message deleted: {message.content[:50]}...")

800

else:

801

print(f"Uncached message deleted in {channel.name} (ID: {payload.message_id})")

802

803

# Log to database or external service

804

await log_message_deletion({

805

'message_id': payload.message_id,

806

'channel_id': payload.channel_id,

807

'guild_id': payload.guild_id,

808

'cached': payload.cached_message is not None,

809

'timestamp': datetime.now()

810

})

811

812

@bot.event

813

async def on_raw_message_edit(payload: nextcord.RawMessageUpdateEvent):

814

"""Called when a message is edited.

815

816

This is called even if the message is not in the bot's cache.

817

818

Parameters

819

----------

820

payload: nextcord.RawMessageUpdateEvent

821

The raw event payload.

822

"""

823

# Skip if partial data (e.g., embed updates)

824

if 'content' not in payload.data:

825

return

826

827

channel = bot.get_channel(payload.channel_id)

828

if not channel:

829

return

830

831

message_id = payload.message_id

832

new_content = payload.data['content']

833

834

# Check if we have cached versions

835

if payload.cached_message:

836

old_content = payload.cached_message.content

837

author = payload.cached_message.author

838

839

print(f"Message edit by {author}: {old_content[:50]}... โ†’ {new_content[:50]}...")

840

else:

841

print(f"Uncached message edited in {channel.name} (ID: {message_id})")

842

843

# Log edit to database

844

await log_message_edit({

845

'message_id': message_id,

846

'channel_id': payload.channel_id,

847

'guild_id': payload.guild_id,

848

'new_content': new_content,

849

'cached': payload.cached_message is not None,

850

'timestamp': datetime.now()

851

})

852

853

@bot.event

854

async def on_raw_reaction_add(payload: nextcord.RawReactionActionEvent):

855

"""Called when a reaction is added to a message.

856

857

This is called even if the message is not in the bot's cache.

858

859

Parameters

860

----------

861

payload: nextcord.RawReactionActionEvent

862

The raw event payload.

863

"""

864

# Skip bot reactions

865

if payload.user_id == bot.user.id:

866

return

867

868

# Get the channel and guild

869

channel = bot.get_channel(payload.channel_id)

870

guild = bot.get_guild(payload.guild_id) if payload.guild_id else None

871

872

if not channel:

873

return

874

875

# Get user and emoji info

876

user = bot.get_user(payload.user_id)

877

emoji = payload.emoji

878

879

print(f"Reaction {emoji} added by {user} to message {payload.message_id}")

880

881

# Handle starboard functionality

882

if str(emoji) == 'โญ':

883

await handle_starboard_reaction(payload, added=True)

884

885

# Handle role reactions for uncached messages

886

await handle_role_reactions(payload, added=True)

887

888

@bot.event

889

async def on_raw_reaction_remove(payload: nextcord.RawReactionActionEvent):

890

"""Called when a reaction is removed from a message.

891

892

Parameters

893

----------

894

payload: nextcord.RawReactionActionEvent

895

The raw event payload.

896

"""

897

# Skip bot reactions

898

if payload.user_id == bot.user.id:

899

return

900

901

user = bot.get_user(payload.user_id)

902

emoji = payload.emoji

903

904

print(f"Reaction {emoji} removed by {user} from message {payload.message_id}")

905

906

# Handle starboard functionality

907

if str(emoji) == 'โญ':

908

await handle_starboard_reaction(payload, added=False)

909

910

# Handle role reactions

911

await handle_role_reactions(payload, added=False)

912

913

@bot.event

914

async def on_raw_reaction_clear(payload: nextcord.RawReactionClearEvent):

915

"""Called when all reactions are cleared from a message.

916

917

Parameters

918

----------

919

payload: nextcord.RawReactionClearEvent

920

The raw event payload.

921

"""

922

print(f"All reactions cleared from message {payload.message_id}")

923

924

# Remove from starboard if applicable

925

await remove_from_starboard(payload.message_id)

926

927

# Advanced raw event handlers

928

async def handle_starboard_reaction(payload: nextcord.RawReactionActionEvent, added: bool):

929

"""Handle starboard functionality for raw reactions."""

930

931

# Get the message (try cache first, then fetch)

932

channel = bot.get_channel(payload.channel_id)

933

if not channel:

934

return

935

936

try:

937

message = await channel.fetch_message(payload.message_id)

938

except nextcord.NotFound:

939

return

940

941

# Skip bot messages

942

if message.author.bot:

943

return

944

945

# Count star reactions

946

star_count = 0

947

for reaction in message.reactions:

948

if str(reaction.emoji) == 'โญ':

949

star_count = reaction.count

950

break

951

952

# Starboard threshold

953

if star_count >= 3:

954

await add_to_starboard(message, star_count)

955

else:

956

await remove_from_starboard(message.id)

957

958

async def handle_role_reactions(payload: nextcord.RawReactionActionEvent, added: bool):

959

"""Handle role reactions for raw events."""

960

961

# Check if this is a role reaction message

962

if payload.message_id != ROLE_MESSAGE_ID: # Configure this

963

return

964

965

guild = bot.get_guild(payload.guild_id)

966

if not guild:

967

return

968

969

member = guild.get_member(payload.user_id)

970

if not member or member.bot:

971

return

972

973

# Role mapping

974

role_mapping = {

975

'๐ŸŽฎ': 'Gamer',

976

'๐ŸŽต': 'Music Lover',

977

'๐Ÿ“š': 'Bookworm',

978

'๐ŸŽจ': 'Artist'

979

}

980

981

role_name = role_mapping.get(str(payload.emoji))

982

if not role_name:

983

return

984

985

role = nextcord.utils.get(guild.roles, name=role_name)

986

if not role:

987

return

988

989

try:

990

if added:

991

await member.add_roles(role, reason="Role reaction")

992

print(f"Added {role.name} to {member} via raw reaction")

993

else:

994

await member.remove_roles(role, reason="Role reaction removed")

995

print(f"Removed {role.name} from {member} via raw reaction")

996

except nextcord.Forbidden:

997

print(f"Cannot modify roles for {member}")

998

999

# Database logging functions (implement according to your database)

1000

async def log_message_deletion(data: dict):

1001

"""Log message deletion to database."""

1002

# Implement database logging

1003

pass

1004

1005

async def log_message_edit(data: dict):

1006

"""Log message edit to database."""

1007

# Implement database logging

1008

pass

1009

1010

async def add_to_starboard(message: nextcord.Message, star_count: int):

1011

"""Add message to starboard."""

1012

# Implement starboard functionality

1013

pass

1014

1015

async def remove_from_starboard(message_id: int):

1016

"""Remove message from starboard."""

1017

# Implement starboard functionality

1018

pass

1019

1020

async def cleanup_guild_data(guild_id: int):

1021

"""Clean up guild-specific data when bot leaves."""

1022

# Implement cleanup logic

1023

pass

1024

```

1025

1026

## Event Monitoring and Analytics

1027

1028

Advanced event monitoring for bot analytics and performance tracking.

1029

1030

### Event Statistics and Monitoring { .api }

1031

1032

```python

1033

from collections import defaultdict, deque

1034

import time

1035

1036

class EventMonitor:

1037

"""Monitor and track bot events for analytics."""

1038

1039

def __init__(self, bot):

1040

self.bot = bot

1041

self.event_counts = defaultdict(int)

1042

self.event_times = defaultdict(lambda: deque(maxlen=1000)) # Last 1000 events

1043

self.guild_stats = defaultdict(lambda: defaultdict(int))

1044

self.user_activity = defaultdict(lambda: defaultdict(int))

1045

self.start_time = time.time()

1046

1047

def track_event(self, event_name: str, guild_id: Optional[int] = None, user_id: Optional[int] = None):

1048

"""Track an event occurrence."""

1049

current_time = time.time()

1050

1051

# Global event tracking

1052

self.event_counts[event_name] += 1

1053

self.event_times[event_name].append(current_time)

1054

1055

# Guild-specific tracking

1056

if guild_id:

1057

self.guild_stats[guild_id][event_name] += 1

1058

1059

# User-specific tracking

1060

if user_id:

1061

self.user_activity[user_id][event_name] += 1

1062

1063

def get_event_rate(self, event_name: str, window_seconds: int = 3600) -> float:

1064

"""Get event rate per second over the specified window."""

1065

current_time = time.time()

1066

cutoff_time = current_time - window_seconds

1067

1068

recent_events = [t for t in self.event_times[event_name] if t > cutoff_time]

1069

return len(recent_events) / window_seconds if recent_events else 0.0

1070

1071

def get_statistics(self) -> Dict[str, Any]:

1072

"""Get comprehensive event statistics."""

1073

uptime = time.time() - self.start_time

1074

1075

stats = {

1076

'uptime_seconds': uptime,

1077

'uptime_formatted': f"{uptime // 3600:.0f}h {(uptime % 3600) // 60:.0f}m",

1078

'total_events': sum(self.event_counts.values()),

1079

'event_counts': dict(self.event_counts),

1080

'event_rates_per_hour': {},

1081

'top_guilds': {},

1082

'top_users': {}

1083

}

1084

1085

# Calculate event rates

1086

for event_name in self.event_counts:

1087

rate = self.get_event_rate(event_name, 3600) # Last hour

1088

stats['event_rates_per_hour'][event_name] = round(rate * 3600, 2)

1089

1090

# Top guilds by activity

1091

guild_totals = {guild_id: sum(events.values()) for guild_id, events in self.guild_stats.items()}

1092

top_guilds = sorted(guild_totals.items(), key=lambda x: x[1], reverse=True)[:10]

1093

1094

for guild_id, total in top_guilds:

1095

guild = self.bot.get_guild(guild_id)

1096

guild_name = guild.name if guild else f"Unknown ({guild_id})"

1097

stats['top_guilds'][guild_name] = total

1098

1099

# Top users by activity

1100

user_totals = {user_id: sum(events.values()) for user_id, events in self.user_activity.items()}

1101

top_users = sorted(user_totals.items(), key=lambda x: x[1], reverse=True)[:10]

1102

1103

for user_id, total in top_users:

1104

user = self.bot.get_user(user_id)

1105

user_name = str(user) if user else f"Unknown ({user_id})"

1106

stats['top_users'][user_name] = total

1107

1108

return stats

1109

1110

# Initialize event monitor

1111

event_monitor = EventMonitor(bot)

1112

1113

# Enhanced event handlers with monitoring

1114

@bot.event

1115

async def on_message(message: nextcord.Message):

1116

"""Enhanced message handler with monitoring."""

1117

event_monitor.track_event('message', message.guild.id if message.guild else None, message.author.id)

1118

1119

# Skip bot messages

1120

if message.author.bot:

1121

return

1122

1123

# Track message characteristics

1124

if len(message.content) > 100:

1125

event_monitor.track_event('long_message', message.guild.id if message.guild else None)

1126

1127

if message.attachments:

1128

event_monitor.track_event('message_with_attachment', message.guild.id if message.guild else None)

1129

1130

if message.mentions:

1131

event_monitor.track_event('message_with_mentions', message.guild.id if message.guild else None)

1132

1133

# Process commands

1134

await bot.process_commands(message)

1135

1136

@bot.event

1137

async def on_member_join(member: nextcord.Member):

1138

"""Enhanced member join handler with monitoring."""

1139

event_monitor.track_event('member_join', member.guild.id, member.id)

1140

1141

# Track account age

1142

account_age_days = (datetime.now() - member.created_at.replace(tzinfo=None)).days

1143

1144

if account_age_days < 7:

1145

event_monitor.track_event('new_account_join', member.guild.id)

1146

elif account_age_days < 30:

1147

event_monitor.track_event('young_account_join', member.guild.id)

1148

1149

# Original join logic here...

1150

1151

@bot.event

1152

async def on_command(ctx: commands.Context):

1153

"""Track command usage."""

1154

event_monitor.track_event('command_used', ctx.guild.id if ctx.guild else None, ctx.author.id)

1155

event_monitor.track_event(f'command_{ctx.command.name}', ctx.guild.id if ctx.guild else None, ctx.author.id)

1156

1157

@bot.event

1158

async def on_command_error(ctx: commands.Context, error):

1159

"""Track command errors."""

1160

event_monitor.track_event('command_error', ctx.guild.id if ctx.guild else None, ctx.author.id)

1161

event_monitor.track_event(f'error_{type(error).__name__}', ctx.guild.id if ctx.guild else None)

1162

1163

# Statistics command

1164

@bot.command()

1165

@commands.is_owner()

1166

async def event_stats(ctx):

1167

"""Show bot event statistics."""

1168

stats = event_monitor.get_statistics()

1169

1170

embed = nextcord.Embed(

1171

title="๐Ÿ“Š Bot Event Statistics",

1172

color=nextcord.Color.blue(),

1173

timestamp=datetime.now()

1174

)

1175

1176

# Overview

1177

embed.add_field(

1178

name="๐Ÿ“ˆ Overview",

1179

value=f"**Uptime:** {stats['uptime_formatted']}\n"

1180

f"**Total Events:** {stats['total_events']:,}",

1181

inline=False

1182

)

1183

1184

# Top events

1185

top_events = sorted(stats['event_counts'].items(), key=lambda x: x[1], reverse=True)[:5]

1186

event_text = []

1187

for event, count in top_events:

1188

rate = stats['event_rates_per_hour'].get(event, 0)

1189

event_text.append(f"**{event}:** {count:,} ({rate}/hr)")

1190

1191

embed.add_field(

1192

name="๐Ÿ”ฅ Top Events",

1193

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

1194

inline=True

1195

)

1196

1197

# Top guilds

1198

if stats['top_guilds']:

1199

guild_text = []

1200

for guild_name, total in list(stats['top_guilds'].items())[:5]:

1201

guild_text.append(f"**{guild_name[:20]}:** {total:,}")

1202

1203

embed.add_field(

1204

name="๐Ÿ† Top Servers",

1205

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

1206

inline=True

1207

)

1208

1209

await ctx.send(embed=embed)

1210

1211

# Performance monitoring

1212

class PerformanceMonitor:

1213

"""Monitor event processing performance."""

1214

1215

def __init__(self):

1216

self.processing_times = defaultdict(list)

1217

self.slow_events = deque(maxlen=100) # Last 100 slow events

1218

1219

def time_event(self, event_name: str):

1220

"""Context manager to time event processing."""

1221

return EventTimer(self, event_name)

1222

1223

class EventTimer:

1224

"""Timer context manager for events."""

1225

1226

def __init__(self, monitor: PerformanceMonitor, event_name: str):

1227

self.monitor = monitor

1228

self.event_name = event_name

1229

self.start_time = None

1230

1231

def __enter__(self):

1232

self.start_time = time.perf_counter()

1233

return self

1234

1235

def __exit__(self, exc_type, exc_val, exc_tb):

1236

duration = time.perf_counter() - self.start_time

1237

self.monitor.processing_times[self.event_name].append(duration)

1238

1239

# Track slow events (> 100ms)

1240

if duration > 0.1:

1241

self.monitor.slow_events.append({

1242

'event': self.event_name,

1243

'duration': duration,

1244

'timestamp': time.time(),

1245

'had_error': exc_type is not None

1246

})

1247

1248

performance_monitor = PerformanceMonitor()

1249

1250

# Example of timed event handler

1251

@bot.event

1252

async def on_message_with_timing(message: nextcord.Message):

1253

"""Message handler with performance monitoring."""

1254

with performance_monitor.time_event('on_message'):

1255

# Original message handling logic

1256

await on_message(message)

1257

```

1258

1259

This comprehensive documentation covers all aspects of nextcord's event system, providing developers with robust tools for handling Discord events and building responsive bot applications.