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.