A Feature-rich Discord Bot Framework for Python with comprehensive API coverage and modern interfaces
—
Comprehensive event system covering Discord gateway events and internal bot events.
The interactions library provides a rich event system with two main categories:
from interactions import listen, Client, events
bot = Client(token="TOKEN")
@listen()
async def on_message_create(event: events.MessageCreate):
"""Listen to all message create events"""
message = event.message
print(f"Message from {message.author}: {message.content}")
@listen(events.GuildJoin)
async def on_guild_join(event: events.GuildJoin):
"""Listen to guild join events"""
guild = event.guild
print(f"Joined guild: {guild.name} ({guild.id})")@listen(events.MessageCreate, events.MessageUpdate)
async def on_message_event(event):
"""Listen to both message create and update events"""
if isinstance(event, events.MessageCreate):
print(f"New message: {event.message.content}")
elif isinstance(event, events.MessageUpdate):
print(f"Message edited: {event.after.content}")from interactions import Extension, listen
class EventExtension(Extension):
@listen()
async def on_ready(self, event: events.Ready):
"""Extension-based event listener"""
print(f"Bot ready! Logged in as {self.bot.user}")
@listen(events.MemberAdd)
async def on_member_join(self, event: events.MemberAdd):
"""Welcome new members"""
member = event.member
channel = member.guild.system_channel
if channel:
await channel.send(f"Welcome {member.mention} to {member.guild.name}!")@listen(events.Ready)
async def on_ready(event: events.Ready):
"""Bot is ready and connected"""
user = event.user
guilds = len(event.guilds)
print(f"Ready! Logged in as {user} in {guilds} guilds")
@listen(events.Connect)
async def on_connect(event: events.Connect):
"""Bot connected to gateway"""
print("Connected to Discord gateway")
@listen(events.Disconnect)
async def on_disconnect(event: events.Disconnect):
"""Bot disconnected from gateway"""
print("Disconnected from Discord gateway")
@listen(events.Resume)
async def on_resume(event: events.Resume):
"""Gateway connection resumed"""
print("Gateway connection resumed")@listen(events.GuildJoin)
async def on_guild_join(event: events.GuildJoin):
"""Bot joins a new guild"""
guild = event.guild
print(f"Joined guild: {guild.name} with {guild.member_count} members")
@listen(events.GuildLeft)
async def on_guild_leave(event: events.GuildLeft):
"""Bot leaves/gets kicked from guild"""
guild = event.guild
print(f"Left guild: {guild.name}")
@listen(events.GuildUpdate)
async def on_guild_update(event: events.GuildUpdate):
"""Guild settings updated"""
before = event.before
after = event.after
print(f"Guild {after.name} was updated")
@listen(events.GuildAvailable)
async def on_guild_available(event: events.GuildAvailable):
"""Guild becomes available after outage"""
guild = event.guild
print(f"Guild {guild.name} is now available")
@listen(events.GuildUnavailable)
async def on_guild_unavailable(event: events.GuildUnavailable):
"""Guild becomes unavailable due to outage"""
guild = event.guild
print(f"Guild {guild.name} is now unavailable")@listen(events.MemberAdd)
async def on_member_join(event: events.MemberAdd):
"""New member joins guild"""
member = event.member
guild = member.guild
# Send welcome message
if guild.system_channel:
embed = Embed(
title="Welcome!",
description=f"Welcome {member.mention} to {guild.name}!",
color=0x00ff00
)
await guild.system_channel.send(embed=embed)
@listen(events.MemberRemove)
async def on_member_leave(event: events.MemberRemove):
"""Member leaves guild"""
member = event.member
print(f"{member} left {member.guild.name}")
@listen(events.MemberUpdate)
async def on_member_update(event: events.MemberUpdate):
"""Member profile updated (roles, nick, etc.)"""
before = event.before
after = event.after
# Check if roles changed
if before.roles != after.roles:
added_roles = set(after.roles) - set(before.roles)
removed_roles = set(before.roles) - set(after.roles)
for role in added_roles:
print(f"{after} gained role: {role.name}")
for role in removed_roles:
print(f"{after} lost role: {role.name}")@listen(events.MessageCreate)
async def on_message(event: events.MessageCreate):
"""New message sent"""
message = event.message
# Ignore bot messages
if message.author.bot:
return
# Auto-react to messages with "python"
if "python" in message.content.lower():
await message.add_reaction("🐍")
@listen(events.MessageUpdate)
async def on_message_edit(event: events.MessageUpdate):
"""Message was edited"""
before = event.before
after = event.after
if before and after and before.content != after.content:
print(f"Message edited in #{after.channel.name}")
print(f"Before: {before.content}")
print(f"After: {after.content}")
@listen(events.MessageDelete)
async def on_message_delete(event: events.MessageDelete):
"""Message was deleted"""
message = event.message
print(f"Message deleted in #{message.channel.name}: {message.content}")
@listen(events.MessageDeleteBulk)
async def on_bulk_delete(event: events.MessageDeleteBulk):
"""Multiple messages deleted"""
messages = event.messages
channel = messages[0].channel if messages else None
print(f"Bulk deleted {len(messages)} messages in #{channel.name}")@listen(events.MessageReactionAdd)
async def on_reaction_add(event: events.MessageReactionAdd):
"""User adds reaction to message"""
reaction = event.reaction
user = event.user
message = event.message
# Role reaction system example
if str(reaction.emoji) == "👍" and not user.bot:
role = message.guild.get_role(THUMBS_UP_ROLE_ID)
if role:
await user.add_role(role)
@listen(events.MessageReactionRemove)
async def on_reaction_remove(event: events.MessageReactionRemove):
"""User removes reaction from message"""
reaction = event.reaction
user = event.user
if str(reaction.emoji) == "👍" and not user.bot:
role = event.message.guild.get_role(THUMBS_UP_ROLE_ID)
if role:
await user.remove_role(role)
@listen(events.MessageReactionRemoveAll)
async def on_reactions_clear(event: events.MessageReactionRemoveAll):
"""All reactions removed from message"""
message = event.message
print(f"All reactions cleared from message in #{message.channel.name}")@listen(events.ChannelCreate)
async def on_channel_create(event: events.ChannelCreate):
"""New channel created"""
channel = event.channel
print(f"New channel created: #{channel.name}")
@listen(events.ChannelUpdate)
async def on_channel_update(event: events.ChannelUpdate):
"""Channel settings updated"""
before = event.before
after = event.after
print(f"Channel #{after.name} was updated")
@listen(events.ChannelDelete)
async def on_channel_delete(event: events.ChannelDelete):
"""Channel deleted"""
channel = event.channel
print(f"Channel deleted: #{channel.name}")
@listen(events.ChannelPinsUpdate)
async def on_pins_update(event: events.ChannelPinsUpdate):
"""Channel pins updated"""
channel = event.channel
print(f"Pins updated in #{channel.name}")@listen(events.ThreadCreate)
async def on_thread_create(event: events.ThreadCreate):
"""New thread created"""
thread = event.thread
print(f"Thread created: {thread.name}")
@listen(events.ThreadUpdate)
async def on_thread_update(event: events.ThreadUpdate):
"""Thread updated"""
before = event.before
after = event.after
print(f"Thread updated: {after.name}")
@listen(events.ThreadDelete)
async def on_thread_delete(event: events.ThreadDelete):
"""Thread deleted"""
thread = event.thread
print(f"Thread deleted: {thread.name}")
@listen(events.ThreadMemberUpdate)
async def on_thread_member_update(event: events.ThreadMemberUpdate):
"""User's thread membership updated"""
member = event.member
thread = event.thread
print(f"{member.user} updated in thread {thread.name}")@listen(events.VoiceStateUpdate)
async def on_voice_state_update(event: events.VoiceStateUpdate):
"""User's voice state changed"""
before = event.before
after = event.after
member = after.member if after else before.member
# User joined voice channel
if not before.channel and after.channel:
print(f"{member} joined voice channel: {after.channel.name}")
# User left voice channel
elif before.channel and not after.channel:
print(f"{member} left voice channel: {before.channel.name}")
# User moved between channels
elif before.channel != after.channel:
print(f"{member} moved from {before.channel.name} to {after.channel.name}")
# Convenience voice events
@listen(events.VoiceUserJoin)
async def on_voice_join(event: events.VoiceUserJoin):
"""User joins voice channel (convenience event)"""
member = event.member
channel = event.channel
print(f"{member} joined {channel.name}")
@listen(events.VoiceUserLeave)
async def on_voice_leave(event: events.VoiceUserLeave):
"""User leaves voice channel (convenience event)"""
member = event.member
channel = event.channel
print(f"{member} left {channel.name}")
@listen(events.VoiceUserMove)
async def on_voice_move(event: events.VoiceUserMove):
"""User moves between voice channels"""
member = event.member
before_channel = event.before_channel
after_channel = event.after_channel
print(f"{member} moved from {before_channel.name} to {after_channel.name}")@listen(events.RoleCreate)
async def on_role_create(event: events.RoleCreate):
"""New role created"""
role = event.role
guild = event.guild
print(f"Role created in {guild.name}: {role.name}")
@listen(events.RoleUpdate)
async def on_role_update(event: events.RoleUpdate):
"""Role updated"""
before = event.before
after = event.after
print(f"Role updated: {after.name}")
@listen(events.RoleDelete)
async def on_role_delete(event: events.RoleDelete):
"""Role deleted"""
role = event.role
guild = event.guild
print(f"Role deleted from {guild.name}: {role.name}")@listen(events.BanCreate)
async def on_member_ban(event: events.BanCreate):
"""Member banned from guild"""
ban = event.ban
user = ban.user
guild = event.guild
reason = ban.reason or "No reason provided"
print(f"{user} was banned from {guild.name}: {reason}")
@listen(events.BanRemove)
async def on_member_unban(event: events.BanRemove):
"""Member unbanned from guild"""
user = event.user
guild = event.guild
print(f"{user} was unbanned from {guild.name}")@listen(events.InteractionCreate)
async def on_interaction(event: events.InteractionCreate):
"""Any interaction received"""
interaction = event.interaction
print(f"Interaction received: {interaction.type}")@listen(events.Startup)
async def on_startup(event: events.Startup):
"""Bot starting up"""
print("Bot is starting up...")
@listen(events.Login)
async def on_login(event: events.Login):
"""Bot logged in"""
print("Bot has logged in to Discord")
@listen(events.Ready)
async def on_ready(event: events.Ready):
"""Bot is ready to receive events"""
print(f"Bot is ready! Logged in as {event.user}")
@listen(events.WebsocketReady)
async def on_websocket_ready(event: events.WebsocketReady):
"""WebSocket connection established"""
print("WebSocket connection ready")@listen(events.CommandCompletion)
async def on_command_complete(event: events.CommandCompletion):
"""Command completed successfully"""
ctx = event.ctx
command = event.command
print(f"Command {command.name} completed by {ctx.author}")
@listen(events.CommandError)
async def on_command_error(event: events.CommandError):
"""Command encountered an error"""
ctx = event.ctx
error = event.error
command = event.command
print(f"Error in command {command.name}: {error}")
# Send error message to user
await ctx.send(f"An error occurred: {str(error)}", ephemeral=True)@listen(events.Component)
async def on_component_interaction(event: events.Component):
"""Generic component interaction"""
ctx = event.ctx
print(f"Component interaction: {ctx.custom_id}")
@listen(events.ComponentCompletion)
async def on_component_complete(event: events.ComponentCompletion):
"""Component interaction completed"""
ctx = event.ctx
print(f"Component {ctx.custom_id} completed")
@listen(events.ComponentError)
async def on_component_error(event: events.ComponentError):
"""Component interaction error"""
ctx = event.ctx
error = event.error
print(f"Component error in {ctx.custom_id}: {error}")
@listen(events.ButtonPressed)
async def on_button_press(event: events.ButtonPressed):
"""Button was pressed (convenience event)"""
ctx = event.ctx
print(f"Button pressed: {ctx.custom_id}")
@listen(events.Select)
async def on_select_menu(event: events.Select):
"""Select menu used (convenience event)"""
ctx = event.ctx
values = ctx.values
print(f"Select menu {ctx.custom_id} used with values: {values}")@listen(events.ModalCompletion)
async def on_modal_complete(event: events.ModalCompletion):
"""Modal submitted successfully"""
ctx = event.ctx
print(f"Modal {ctx.custom_id} submitted")
@listen(events.ModalError)
async def on_modal_error(event: events.ModalError):
"""Modal submission error"""
ctx = event.ctx
error = event.error
print(f"Modal error in {ctx.custom_id}: {error}")@listen(events.ExtensionLoad)
async def on_extension_load(event: events.ExtensionLoad):
"""Extension loaded"""
extension = event.extension
print(f"Extension loaded: {extension.name}")
@listen(events.ExtensionUnload)
async def on_extension_unload(event: events.ExtensionUnload):
"""Extension unloaded"""
extension = event.extension
print(f"Extension unloaded: {extension.name}")@listen(events.Error)
async def on_general_error(event: events.Error):
"""General error occurred"""
error = event.error
print(f"General error: {error}")
@listen(events.AutocompleteError)
async def on_autocomplete_error(event: events.AutocompleteError):
"""Autocomplete error"""
ctx = event.ctx
error = event.error
print(f"Autocomplete error: {error}")class BaseEvent:
"""Base class for all events"""
bot: Client
class RawGatewayEvent(BaseEvent):
"""Raw gateway event data"""
data: dict
sequence: int
event_name: str
class GuildEvent(BaseEvent):
"""Base for guild-related events"""
guild: Guild@listen(events.MessageCreate)
async def on_message_in_channel(event: events.MessageCreate):
"""Only process messages in specific channel"""
message = event.message
# Filter by channel
if message.channel.id != SPECIFIC_CHANNEL_ID:
return
# Process message
await process_message(message)
@listen(events.MemberAdd)
async def on_member_join_specific_guild(event: events.MemberAdd):
"""Only process joins in specific guild"""
member = event.member
if member.guild.id != SPECIFIC_GUILD_ID:
return
await welcome_member(member)@listen(events.MessageCreate)
async def on_message_with_conditions(event: events.MessageCreate):
"""Message event with multiple conditions"""
message = event.message
# Skip bot messages
if message.author.bot:
return
# Only process messages with attachments
if not message.attachments:
return
# Only process in guild channels
if not message.guild:
return
# Process the message
await handle_attachment_message(message)@listen(events.MessageUpdate)
async def on_message_edit_detailed(event: events.MessageUpdate):
"""Access detailed message edit data"""
before = event.before # Message before edit (may be None if not cached)
after = event.after # Message after edit
if before and after:
# Compare content changes
if before.content != after.content:
print(f"Content changed from: {before.content}")
print(f"Content changed to: {after.content}")
# Check embed changes
if before.embeds != after.embeds:
print("Embeds were modified")class MyBot(Client):
def __init__(self):
super().__init__(token="TOKEN")
@listen()
async def on_ready(self, event: events.Ready):
"""Method-based event listener"""
print(f"{self.user} is ready!")
@listen(events.MessageCreate)
async def handle_messages(self, event: events.MessageCreate):
"""Handle messages in class context"""
message = event.message
if message.content.startswith(f"<@{self.user.id}>"):
await message.reply("You mentioned me!")@listen(events.GuildJoin)
@listen(events.GuildAvailable)
async def on_guild_accessible(event):
"""Listen to multiple related events"""
guild = event.guild
print(f"Guild accessible: {guild.name}")Install with Tessl CLI
npx tessl i tessl/pypi-discord-py-interactions