A Python wrapper for the Discord API forked from discord.py
—
Event handling system for Discord gateway events and raw event data for advanced use cases with comprehensive event monitoring capabilities.
Core event handling mechanism for responding to Discord gateway events.
import nextcord
from nextcord.ext import commands
from typing import Any, Callable, Optional, Dict, List
from datetime import datetime
# Event registration using decorator
@bot.event
async def on_ready():
"""Called when the client has successfully connected to Discord.
This event is called when the bot has finished logging in and setting up.
It's only called once per login session.
"""
print(f'{bot.user} has connected to Discord!')
print(f'Bot is in {len(bot.guilds)} guilds')
print(f'Connected at: {datetime.now()}')
# Set bot status
activity = nextcord.Activity(
type=nextcord.ActivityType.watching,
name=f"{len(bot.guilds)} servers"
)
await bot.change_presence(status=nextcord.Status.online, activity=activity)
# Manual event registration
async def my_event_handler():
"""Custom event handler function."""
print("Custom event triggered!")
# Register event manually
bot.add_listener(my_event_handler, 'on_ready')
# Multiple listeners for the same event
@bot.event
async def on_ready():
"""First ready handler."""
print("First ready handler called")
@bot.listen('on_ready') # Use listen() to add additional handlers
async def second_ready_handler():
"""Second ready handler."""
print("Second ready handler called")
# Event with parameters
@bot.event
async def on_message(message: nextcord.Message):
"""Called when a message is sent in a channel the bot can see.
Parameters
----------
message: nextcord.Message
The message that was sent.
"""
# Ignore messages from bots
if message.author.bot:
return
print(f'{message.author}: {message.content}')
# Process commands (required for command framework)
await bot.process_commands(message)
# Event error handling
@bot.event
async def on_error(event: str, *args, **kwargs):
"""Called when an event raises an uncaught exception.
Parameters
----------
event: str
The name of the event that raised the exception.
*args
The positional arguments passed to the event.
**kwargs
The keyword arguments passed to the event.
"""
import traceback
print(f'An error occurred in {event}:')
traceback.print_exc()
# Log error to file or database
with open('error_log.txt', 'a') as f:
f.write(f'{datetime.now()}: Error in {event}\n')
traceback.print_exc(file=f)
f.write('\n')Events related to bot connection and disconnection states.
@bot.event
async def on_connect():
"""Called when the client has successfully connected to Discord.
This is different from on_ready in that it's called every time
the websocket connection is established (including reconnections).
"""
print(f'Connected to Discord at {datetime.now()}')
@bot.event
async def on_disconnect():
"""Called when the client has disconnected from Discord.
This could be due to internet issues, Discord issues, or
the bot being shut down.
"""
print(f'Disconnected from Discord at {datetime.now()}')
@bot.event
async def on_resumed():
"""Called when the client has resumed a session.
This is called when the websocket connection has been resumed
after a disconnect, rather than creating a new session.
"""
print(f'Resumed Discord connection at {datetime.now()}')
# Advanced connection monitoring
class ConnectionMonitor:
"""Monitor connection events and track statistics."""
def __init__(self, bot):
self.bot = bot
self.connection_count = 0
self.disconnect_count = 0
self.resume_count = 0
self.first_connect_time = None
self.last_disconnect_time = None
self.uptime_start = None
@commands.Cog.listener()
async def on_connect(self):
"""Track connection events."""
self.connection_count += 1
current_time = datetime.now()
if self.first_connect_time is None:
self.first_connect_time = current_time
self.uptime_start = current_time
print(f'Connection #{self.connection_count} established')
@commands.Cog.listener()
async def on_disconnect(self):
"""Track disconnection events."""
self.disconnect_count += 1
self.last_disconnect_time = datetime.now()
print(f'Disconnect #{self.disconnect_count} occurred')
@commands.Cog.listener()
async def on_resumed(self):
"""Track resume events."""
self.resume_count += 1
print(f'Resume #{self.resume_count} occurred')
@commands.Cog.listener()
async def on_ready(self):
"""Log ready state with connection info."""
current_time = datetime.now()
if self.uptime_start:
uptime = current_time - self.uptime_start
print(f'Bot ready! Uptime: {uptime}')
print(f'Connection stats: {self.connection_count} connects, '
f'{self.disconnect_count} disconnects, {self.resume_count} resumes')
# Add the connection monitor
bot.add_cog(ConnectionMonitor(bot))
# Sharded connection events (for AutoShardedBot)
@bot.event
async def on_shard_connect(shard_id: int):
"""Called when a shard connects.
Parameters
----------
shard_id: int
The ID of the shard that connected.
"""
print(f'Shard {shard_id} connected')
@bot.event
async def on_shard_disconnect(shard_id: int):
"""Called when a shard disconnects.
Parameters
----------
shard_id: int
The ID of the shard that disconnected.
"""
print(f'Shard {shard_id} disconnected')
@bot.event
async def on_shard_ready(shard_id: int):
"""Called when a shard becomes ready.
Parameters
----------
shard_id: int
The ID of the shard that became ready.
"""
print(f'Shard {shard_id} is ready')
@bot.event
async def on_shard_resumed(shard_id: int):
"""Called when a shard resumes.
Parameters
----------
shard_id: int
The ID of the shard that resumed.
"""
print(f'Shard {shard_id} resumed')Events related to message creation, editing, and deletion.
@bot.event
async def on_message(message: nextcord.Message):
"""Called when a message is created.
Parameters
----------
message: nextcord.Message
The message that was created.
"""
# Skip bot messages
if message.author.bot:
return
# Log message statistics
print(f'Message from {message.author} in {message.channel}: {len(message.content)} chars')
# Auto-moderation example
if any(word in message.content.lower() for word in ['spam', 'advertisement']):
await message.delete()
await message.channel.send(
f"{message.author.mention}, that message was removed for spam.",
delete_after=5
)
return
# Process commands
await bot.process_commands(message)
@bot.event
async def on_message_edit(before: nextcord.Message, after: nextcord.Message):
"""Called when a message is edited.
Parameters
----------
before: nextcord.Message
The message before editing.
after: nextcord.Message
The message after editing.
"""
# Skip bot messages
if before.author.bot:
return
# Skip if content didn't change (embed updates, etc.)
if before.content == after.content:
return
# Log edit to moderation channel
mod_channel = nextcord.utils.get(after.guild.channels, name='mod-logs')
if mod_channel:
embed = nextcord.Embed(
title="📝 Message Edited",
color=nextcord.Color.orange(),
timestamp=datetime.now()
)
embed.add_field(name="Author", value=after.author.mention, inline=True)
embed.add_field(name="Channel", value=after.channel.mention, inline=True)
embed.add_field(name="Message ID", value=after.id, inline=True)
# Show before/after content (truncated)
before_content = before.content[:1000] + "..." if len(before.content) > 1000 else before.content
after_content = after.content[:1000] + "..." if len(after.content) > 1000 else after.content
embed.add_field(name="Before", value=before_content or "*No content*", inline=False)
embed.add_field(name="After", value=after_content or "*No content*", inline=False)
embed.add_field(name="Jump to Message", value=f"[Click here]({after.jump_url})", inline=False)
await mod_channel.send(embed=embed)
@bot.event
async def on_message_delete(message: nextcord.Message):
"""Called when a message is deleted.
Parameters
----------
message: nextcord.Message
The message that was deleted.
"""
# Skip bot messages
if message.author.bot:
return
# Log deletion
mod_channel = nextcord.utils.get(message.guild.channels, name='mod-logs')
if mod_channel:
embed = nextcord.Embed(
title="🗑️ Message Deleted",
color=nextcord.Color.red(),
timestamp=datetime.now()
)
embed.add_field(name="Author", value=message.author.mention, inline=True)
embed.add_field(name="Channel", value=message.channel.mention, inline=True)
embed.add_field(name="Message ID", value=message.id, inline=True)
# Show deleted content (truncated)
content = message.content[:1500] + "..." if len(message.content) > 1500 else message.content
embed.add_field(name="Content", value=content or "*No text content*", inline=False)
# Show attachments if any
if message.attachments:
attachment_info = []
for attachment in message.attachments[:5]: # Show first 5
attachment_info.append(f"• {attachment.filename} ({attachment.size} bytes)")
embed.add_field(
name=f"Attachments ({len(message.attachments)})",
value="\n".join(attachment_info),
inline=False
)
embed.set_footer(text=f"Originally sent", icon_url=message.author.display_avatar.url)
embed.timestamp = message.created_at
await mod_channel.send(embed=embed)
# Bulk message operations
@bot.event
async def on_bulk_message_delete(messages: List[nextcord.Message]):
"""Called when messages are bulk deleted.
Parameters
----------
messages: List[nextcord.Message]
The messages that were deleted.
"""
if not messages:
return
# Get guild and channel from first message
first_message = messages[0]
guild = first_message.guild
channel = first_message.channel
# Log bulk deletion
mod_channel = nextcord.utils.get(guild.channels, name='mod-logs')
if mod_channel:
embed = nextcord.Embed(
title="🗑️ Bulk Message Deletion",
description=f"{len(messages)} messages deleted from {channel.mention}",
color=nextcord.Color.red(),
timestamp=datetime.now()
)
# Show message count by author
author_counts = {}
for message in messages:
author_counts[message.author] = author_counts.get(message.author, 0) + 1
# Top 5 authors
top_authors = sorted(author_counts.items(), key=lambda x: x[1], reverse=True)[:5]
author_text = []
for author, count in top_authors:
author_text.append(f"• {author.mention}: {count} messages")
embed.add_field(name="Messages by Author", value="\n".join(author_text), inline=False)
# Time range
if len(messages) > 1:
oldest = min(messages, key=lambda m: m.created_at)
newest = max(messages, key=lambda m: m.created_at)
embed.add_field(
name="Time Range",
value=f"From {oldest.created_at.strftime('%Y-%m-%d %H:%M')} to {newest.created_at.strftime('%Y-%m-%d %H:%M')}",
inline=False
)
await mod_channel.send(embed=embed)
# Message reaction events
@bot.event
async def on_reaction_add(reaction: nextcord.Reaction, user: nextcord.User):
"""Called when a reaction is added to a message.
Parameters
----------
reaction: nextcord.Reaction
The reaction that was added.
user: nextcord.User
The user who added the reaction.
"""
# Skip bot reactions
if user.bot:
return
message = reaction.message
# Role reaction system example
if message.id == ROLE_MESSAGE_ID: # Configure this
role_mapping = {
'🎮': 'Gamer',
'🎵': 'Music Lover',
'📚': 'Bookworm',
'🎨': 'Artist'
}
role_name = role_mapping.get(str(reaction.emoji))
if role_name:
role = nextcord.utils.get(message.guild.roles, name=role_name)
if role:
member = message.guild.get_member(user.id)
if member:
await member.add_roles(role, reason="Role reaction")
print(f"Added {role.name} to {member}")
@bot.event
async def on_reaction_remove(reaction: nextcord.Reaction, user: nextcord.User):
"""Called when a reaction is removed from a message.
Parameters
----------
reaction: nextcord.Reaction
The reaction that was removed.
user: nextcord.User
The user who removed the reaction.
"""
# Skip bot reactions
if user.bot:
return
message = reaction.message
# Remove role when reaction is removed
if message.id == ROLE_MESSAGE_ID: # Configure this
role_mapping = {
'🎮': 'Gamer',
'🎵': 'Music Lover',
'📚': 'Bookworm',
'🎨': 'Artist'
}
role_name = role_mapping.get(str(reaction.emoji))
if role_name:
role = nextcord.utils.get(message.guild.roles, name=role_name)
if role:
member = message.guild.get_member(user.id)
if member:
await member.remove_roles(role, reason="Role reaction removed")
print(f"Removed {role.name} from {member}")
@bot.event
async def on_reaction_clear(message: nextcord.Message, reactions: List[nextcord.Reaction]):
"""Called when all reactions are cleared from a message.
Parameters
----------
message: nextcord.Message
The message that had reactions cleared.
reactions: List[nextcord.Reaction]
The reactions that were cleared.
"""
print(f"All reactions cleared from message {message.id} in {message.channel}")Events related to guild membership and server changes.
@bot.event
async def on_member_join(member: nextcord.Member):
"""Called when a member joins a guild.
Parameters
----------
member: nextcord.Member
The member who joined.
"""
guild = member.guild
# Send welcome message
welcome_channel = nextcord.utils.get(guild.channels, name='welcome')
if welcome_channel:
embed = nextcord.Embed(
title="👋 Welcome!",
description=f"Welcome to **{guild.name}**, {member.mention}!",
color=nextcord.Color.green(),
timestamp=datetime.now()
)
embed.add_field(name="Member Count", value=f"You are member #{guild.member_count}", inline=True)
embed.add_field(name="Account Created", value=member.created_at.strftime("%B %d, %Y"), inline=True)
embed.set_thumbnail(url=member.display_avatar.url)
embed.set_footer(text=f"ID: {member.id}")
await welcome_channel.send(embed=embed)
# Auto-role assignment
auto_role = nextcord.utils.get(guild.roles, name='Member')
if auto_role:
try:
await member.add_roles(auto_role, reason="Auto-role on join")
print(f"Gave {auto_role.name} to {member}")
except nextcord.Forbidden:
print(f"Failed to give auto-role to {member} - insufficient permissions")
# Log join
log_channel = nextcord.utils.get(guild.channels, name='member-logs')
if log_channel:
embed = nextcord.Embed(
title="📈 Member Joined",
color=nextcord.Color.green(),
timestamp=datetime.now()
)
embed.add_field(name="Member", value=f"{member} ({member.mention})", inline=True)
embed.add_field(name="ID", value=member.id, inline=True)
embed.add_field(name="Total Members", value=guild.member_count, inline=True)
# Account age
account_age = datetime.now() - member.created_at
embed.add_field(name="Account Age", value=f"{account_age.days} days", inline=True)
embed.set_thumbnail(url=member.display_avatar.url)
await log_channel.send(embed=embed)
@bot.event
async def on_member_remove(member: nextcord.Member):
"""Called when a member leaves a guild.
Parameters
----------
member: nextcord.Member
The member who left.
"""
guild = member.guild
# Send farewell message
farewell_channel = nextcord.utils.get(guild.channels, name='farewell')
if farewell_channel:
embed = nextcord.Embed(
title="👋 Goodbye!",
description=f"**{member.display_name}** has left the server.",
color=nextcord.Color.red(),
timestamp=datetime.now()
)
embed.add_field(name="Member Count", value=f"We now have {guild.member_count} members", inline=True)
# Calculate how long they were in the server
if member.joined_at:
time_in_server = datetime.now() - member.joined_at.replace(tzinfo=None)
embed.add_field(name="Time in Server", value=f"{time_in_server.days} days", inline=True)
embed.set_thumbnail(url=member.display_avatar.url)
await farewell_channel.send(embed=embed)
# Log leave
log_channel = nextcord.utils.get(guild.channels, name='member-logs')
if log_channel:
embed = nextcord.Embed(
title="📉 Member Left",
color=nextcord.Color.red(),
timestamp=datetime.now()
)
embed.add_field(name="Member", value=f"{member} ({member.mention})", inline=True)
embed.add_field(name="ID", value=member.id, inline=True)
embed.add_field(name="Total Members", value=guild.member_count, inline=True)
# Show roles they had
if member.roles[1:]: # Skip @everyone
roles = [role.name for role in member.roles[1:][:10]] # First 10 roles
embed.add_field(name="Roles", value=", ".join(roles), inline=False)
embed.set_thumbnail(url=member.display_avatar.url)
await log_channel.send(embed=embed)
@bot.event
async def on_member_update(before: nextcord.Member, after: nextcord.Member):
"""Called when a member updates their profile or guild-specific settings.
Parameters
----------
before: nextcord.Member
The member before the update.
after: nextcord.Member
The member after the update.
"""
# Skip if no relevant changes
if (before.display_name == after.display_name and
before.roles == after.roles and
before.status == after.status):
return
log_channel = nextcord.utils.get(after.guild.channels, name='member-logs')
if not log_channel:
return
embed = nextcord.Embed(
title="👤 Member Updated",
color=nextcord.Color.blue(),
timestamp=datetime.now()
)
embed.add_field(name="Member", value=after.mention, inline=True)
embed.add_field(name="ID", value=after.id, inline=True)
# Check for nickname changes
if before.display_name != after.display_name:
embed.add_field(
name="Nickname Change",
value=f"**Before:** {before.display_name}\n**After:** {after.display_name}",
inline=False
)
# Check for role changes
if before.roles != after.roles:
added_roles = [role for role in after.roles if role not in before.roles]
removed_roles = [role for role in before.roles if role not in after.roles]
if added_roles:
embed.add_field(
name="Roles Added",
value=", ".join([role.mention for role in added_roles]),
inline=False
)
if removed_roles:
embed.add_field(
name="Roles Removed",
value=", ".join([role.mention for role in removed_roles]),
inline=False
)
# Only send if there are actual changes to log
if len(embed.fields) > 2: # More than just member and ID fields
embed.set_thumbnail(url=after.display_avatar.url)
await log_channel.send(embed=embed)
# Guild events
@bot.event
async def on_guild_join(guild: nextcord.Guild):
"""Called when the bot joins a guild.
Parameters
----------
guild: nextcord.Guild
The guild that was joined.
"""
print(f"Joined guild: {guild.name} (ID: {guild.id})")
print(f"Guild has {guild.member_count} members")
# Send a greeting message to the system channel
if guild.system_channel:
embed = nextcord.Embed(
title="👋 Hello!",
description="Thank you for adding me to your server!",
color=nextcord.Color.green()
)
embed.add_field(
name="Getting Started",
value="Use `/help` to see available commands.",
inline=False
)
embed.add_field(
name="Support",
value="Need help? Join our support server!",
inline=False
)
try:
await guild.system_channel.send(embed=embed)
except nextcord.Forbidden:
print(f"Cannot send greeting to {guild.name} - no permission")
@bot.event
async def on_guild_remove(guild: nextcord.Guild):
"""Called when the bot leaves a guild.
Parameters
----------
guild: nextcord.Guild
The guild that was left.
"""
print(f"Left guild: {guild.name} (ID: {guild.id})")
# Clean up any guild-specific data
# This is where you'd remove database entries, etc.
await cleanup_guild_data(guild.id)
@bot.event
async def on_guild_update(before: nextcord.Guild, after: nextcord.Guild):
"""Called when a guild updates.
Parameters
----------
before: nextcord.Guild
The guild before the update.
after: nextcord.Guild
The guild after the update.
"""
changes = []
if before.name != after.name:
changes.append(f"Name: {before.name} → {after.name}")
if before.description != after.description:
changes.append(f"Description changed")
if before.icon != after.icon:
changes.append(f"Icon changed")
if before.banner != after.banner:
changes.append(f"Banner changed")
if before.verification_level != after.verification_level:
changes.append(f"Verification level: {before.verification_level} → {after.verification_level}")
if changes:
log_channel = nextcord.utils.get(after.channels, name='server-logs')
if log_channel:
embed = nextcord.Embed(
title="🏠 Server Updated",
description="\n".join(changes),
color=nextcord.Color.blue(),
timestamp=datetime.now()
)
await log_channel.send(embed=embed)Raw event data for advanced use cases requiring detailed event information.
@bot.event
async def on_raw_message_delete(payload: nextcord.RawMessageDeleteEvent):
"""Called when a message is deleted.
This is called even if the message is not in the bot's cache.
Parameters
----------
payload: nextcord.RawMessageDeleteEvent
The raw event payload.
"""
# Get the channel
channel = bot.get_channel(payload.channel_id)
if not channel:
return
# Check if we have the cached message
if payload.cached_message:
message = payload.cached_message
print(f"Cached message deleted: {message.content[:50]}...")
else:
print(f"Uncached message deleted in {channel.name} (ID: {payload.message_id})")
# Log to database or external service
await log_message_deletion({
'message_id': payload.message_id,
'channel_id': payload.channel_id,
'guild_id': payload.guild_id,
'cached': payload.cached_message is not None,
'timestamp': datetime.now()
})
@bot.event
async def on_raw_message_edit(payload: nextcord.RawMessageUpdateEvent):
"""Called when a message is edited.
This is called even if the message is not in the bot's cache.
Parameters
----------
payload: nextcord.RawMessageUpdateEvent
The raw event payload.
"""
# Skip if partial data (e.g., embed updates)
if 'content' not in payload.data:
return
channel = bot.get_channel(payload.channel_id)
if not channel:
return
message_id = payload.message_id
new_content = payload.data['content']
# Check if we have cached versions
if payload.cached_message:
old_content = payload.cached_message.content
author = payload.cached_message.author
print(f"Message edit by {author}: {old_content[:50]}... → {new_content[:50]}...")
else:
print(f"Uncached message edited in {channel.name} (ID: {message_id})")
# Log edit to database
await log_message_edit({
'message_id': message_id,
'channel_id': payload.channel_id,
'guild_id': payload.guild_id,
'new_content': new_content,
'cached': payload.cached_message is not None,
'timestamp': datetime.now()
})
@bot.event
async def on_raw_reaction_add(payload: nextcord.RawReactionActionEvent):
"""Called when a reaction is added to a message.
This is called even if the message is not in the bot's cache.
Parameters
----------
payload: nextcord.RawReactionActionEvent
The raw event payload.
"""
# Skip bot reactions
if payload.user_id == bot.user.id:
return
# Get the channel and guild
channel = bot.get_channel(payload.channel_id)
guild = bot.get_guild(payload.guild_id) if payload.guild_id else None
if not channel:
return
# Get user and emoji info
user = bot.get_user(payload.user_id)
emoji = payload.emoji
print(f"Reaction {emoji} added by {user} to message {payload.message_id}")
# Handle starboard functionality
if str(emoji) == '⭐':
await handle_starboard_reaction(payload, added=True)
# Handle role reactions for uncached messages
await handle_role_reactions(payload, added=True)
@bot.event
async def on_raw_reaction_remove(payload: nextcord.RawReactionActionEvent):
"""Called when a reaction is removed from a message.
Parameters
----------
payload: nextcord.RawReactionActionEvent
The raw event payload.
"""
# Skip bot reactions
if payload.user_id == bot.user.id:
return
user = bot.get_user(payload.user_id)
emoji = payload.emoji
print(f"Reaction {emoji} removed by {user} from message {payload.message_id}")
# Handle starboard functionality
if str(emoji) == '⭐':
await handle_starboard_reaction(payload, added=False)
# Handle role reactions
await handle_role_reactions(payload, added=False)
@bot.event
async def on_raw_reaction_clear(payload: nextcord.RawReactionClearEvent):
"""Called when all reactions are cleared from a message.
Parameters
----------
payload: nextcord.RawReactionClearEvent
The raw event payload.
"""
print(f"All reactions cleared from message {payload.message_id}")
# Remove from starboard if applicable
await remove_from_starboard(payload.message_id)
# Advanced raw event handlers
async def handle_starboard_reaction(payload: nextcord.RawReactionActionEvent, added: bool):
"""Handle starboard functionality for raw reactions."""
# Get the message (try cache first, then fetch)
channel = bot.get_channel(payload.channel_id)
if not channel:
return
try:
message = await channel.fetch_message(payload.message_id)
except nextcord.NotFound:
return
# Skip bot messages
if message.author.bot:
return
# Count star reactions
star_count = 0
for reaction in message.reactions:
if str(reaction.emoji) == '⭐':
star_count = reaction.count
break
# Starboard threshold
if star_count >= 3:
await add_to_starboard(message, star_count)
else:
await remove_from_starboard(message.id)
async def handle_role_reactions(payload: nextcord.RawReactionActionEvent, added: bool):
"""Handle role reactions for raw events."""
# Check if this is a role reaction message
if payload.message_id != ROLE_MESSAGE_ID: # Configure this
return
guild = bot.get_guild(payload.guild_id)
if not guild:
return
member = guild.get_member(payload.user_id)
if not member or member.bot:
return
# Role mapping
role_mapping = {
'🎮': 'Gamer',
'🎵': 'Music Lover',
'📚': 'Bookworm',
'🎨': 'Artist'
}
role_name = role_mapping.get(str(payload.emoji))
if not role_name:
return
role = nextcord.utils.get(guild.roles, name=role_name)
if not role:
return
try:
if added:
await member.add_roles(role, reason="Role reaction")
print(f"Added {role.name} to {member} via raw reaction")
else:
await member.remove_roles(role, reason="Role reaction removed")
print(f"Removed {role.name} from {member} via raw reaction")
except nextcord.Forbidden:
print(f"Cannot modify roles for {member}")
# Database logging functions (implement according to your database)
async def log_message_deletion(data: dict):
"""Log message deletion to database."""
# Implement database logging
pass
async def log_message_edit(data: dict):
"""Log message edit to database."""
# Implement database logging
pass
async def add_to_starboard(message: nextcord.Message, star_count: int):
"""Add message to starboard."""
# Implement starboard functionality
pass
async def remove_from_starboard(message_id: int):
"""Remove message from starboard."""
# Implement starboard functionality
pass
async def cleanup_guild_data(guild_id: int):
"""Clean up guild-specific data when bot leaves."""
# Implement cleanup logic
passAdvanced event monitoring for bot analytics and performance tracking.
from collections import defaultdict, deque
import time
class EventMonitor:
"""Monitor and track bot events for analytics."""
def __init__(self, bot):
self.bot = bot
self.event_counts = defaultdict(int)
self.event_times = defaultdict(lambda: deque(maxlen=1000)) # Last 1000 events
self.guild_stats = defaultdict(lambda: defaultdict(int))
self.user_activity = defaultdict(lambda: defaultdict(int))
self.start_time = time.time()
def track_event(self, event_name: str, guild_id: Optional[int] = None, user_id: Optional[int] = None):
"""Track an event occurrence."""
current_time = time.time()
# Global event tracking
self.event_counts[event_name] += 1
self.event_times[event_name].append(current_time)
# Guild-specific tracking
if guild_id:
self.guild_stats[guild_id][event_name] += 1
# User-specific tracking
if user_id:
self.user_activity[user_id][event_name] += 1
def get_event_rate(self, event_name: str, window_seconds: int = 3600) -> float:
"""Get event rate per second over the specified window."""
current_time = time.time()
cutoff_time = current_time - window_seconds
recent_events = [t for t in self.event_times[event_name] if t > cutoff_time]
return len(recent_events) / window_seconds if recent_events else 0.0
def get_statistics(self) -> Dict[str, Any]:
"""Get comprehensive event statistics."""
uptime = time.time() - self.start_time
stats = {
'uptime_seconds': uptime,
'uptime_formatted': f"{uptime // 3600:.0f}h {(uptime % 3600) // 60:.0f}m",
'total_events': sum(self.event_counts.values()),
'event_counts': dict(self.event_counts),
'event_rates_per_hour': {},
'top_guilds': {},
'top_users': {}
}
# Calculate event rates
for event_name in self.event_counts:
rate = self.get_event_rate(event_name, 3600) # Last hour
stats['event_rates_per_hour'][event_name] = round(rate * 3600, 2)
# Top guilds by activity
guild_totals = {guild_id: sum(events.values()) for guild_id, events in self.guild_stats.items()}
top_guilds = sorted(guild_totals.items(), key=lambda x: x[1], reverse=True)[:10]
for guild_id, total in top_guilds:
guild = self.bot.get_guild(guild_id)
guild_name = guild.name if guild else f"Unknown ({guild_id})"
stats['top_guilds'][guild_name] = total
# Top users by activity
user_totals = {user_id: sum(events.values()) for user_id, events in self.user_activity.items()}
top_users = sorted(user_totals.items(), key=lambda x: x[1], reverse=True)[:10]
for user_id, total in top_users:
user = self.bot.get_user(user_id)
user_name = str(user) if user else f"Unknown ({user_id})"
stats['top_users'][user_name] = total
return stats
# Initialize event monitor
event_monitor = EventMonitor(bot)
# Enhanced event handlers with monitoring
@bot.event
async def on_message(message: nextcord.Message):
"""Enhanced message handler with monitoring."""
event_monitor.track_event('message', message.guild.id if message.guild else None, message.author.id)
# Skip bot messages
if message.author.bot:
return
# Track message characteristics
if len(message.content) > 100:
event_monitor.track_event('long_message', message.guild.id if message.guild else None)
if message.attachments:
event_monitor.track_event('message_with_attachment', message.guild.id if message.guild else None)
if message.mentions:
event_monitor.track_event('message_with_mentions', message.guild.id if message.guild else None)
# Process commands
await bot.process_commands(message)
@bot.event
async def on_member_join(member: nextcord.Member):
"""Enhanced member join handler with monitoring."""
event_monitor.track_event('member_join', member.guild.id, member.id)
# Track account age
account_age_days = (datetime.now() - member.created_at.replace(tzinfo=None)).days
if account_age_days < 7:
event_monitor.track_event('new_account_join', member.guild.id)
elif account_age_days < 30:
event_monitor.track_event('young_account_join', member.guild.id)
# Original join logic here...
@bot.event
async def on_command(ctx: commands.Context):
"""Track command usage."""
event_monitor.track_event('command_used', ctx.guild.id if ctx.guild else None, ctx.author.id)
event_monitor.track_event(f'command_{ctx.command.name}', ctx.guild.id if ctx.guild else None, ctx.author.id)
@bot.event
async def on_command_error(ctx: commands.Context, error):
"""Track command errors."""
event_monitor.track_event('command_error', ctx.guild.id if ctx.guild else None, ctx.author.id)
event_monitor.track_event(f'error_{type(error).__name__}', ctx.guild.id if ctx.guild else None)
# Statistics command
@bot.command()
@commands.is_owner()
async def event_stats(ctx):
"""Show bot event statistics."""
stats = event_monitor.get_statistics()
embed = nextcord.Embed(
title="📊 Bot Event Statistics",
color=nextcord.Color.blue(),
timestamp=datetime.now()
)
# Overview
embed.add_field(
name="📈 Overview",
value=f"**Uptime:** {stats['uptime_formatted']}\n"
f"**Total Events:** {stats['total_events']:,}",
inline=False
)
# Top events
top_events = sorted(stats['event_counts'].items(), key=lambda x: x[1], reverse=True)[:5]
event_text = []
for event, count in top_events:
rate = stats['event_rates_per_hour'].get(event, 0)
event_text.append(f"**{event}:** {count:,} ({rate}/hr)")
embed.add_field(
name="🔥 Top Events",
value="\n".join(event_text),
inline=True
)
# Top guilds
if stats['top_guilds']:
guild_text = []
for guild_name, total in list(stats['top_guilds'].items())[:5]:
guild_text.append(f"**{guild_name[:20]}:** {total:,}")
embed.add_field(
name="🏆 Top Servers",
value="\n".join(guild_text),
inline=True
)
await ctx.send(embed=embed)
# Performance monitoring
class PerformanceMonitor:
"""Monitor event processing performance."""
def __init__(self):
self.processing_times = defaultdict(list)
self.slow_events = deque(maxlen=100) # Last 100 slow events
def time_event(self, event_name: str):
"""Context manager to time event processing."""
return EventTimer(self, event_name)
class EventTimer:
"""Timer context manager for events."""
def __init__(self, monitor: PerformanceMonitor, event_name: str):
self.monitor = monitor
self.event_name = event_name
self.start_time = None
def __enter__(self):
self.start_time = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
duration = time.perf_counter() - self.start_time
self.monitor.processing_times[self.event_name].append(duration)
# Track slow events (> 100ms)
if duration > 0.1:
self.monitor.slow_events.append({
'event': self.event_name,
'duration': duration,
'timestamp': time.time(),
'had_error': exc_type is not None
})
performance_monitor = PerformanceMonitor()
# Example of timed event handler
@bot.event
async def on_message_with_timing(message: nextcord.Message):
"""Message handler with performance monitoring."""
with performance_monitor.time_event('on_message'):
# Original message handling logic
await on_message(message)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.
Install with Tessl CLI
npx tessl i tessl/pypi-nextcord