A Feature-rich Discord Bot Framework for Python with comprehensive API coverage and modern interfaces
—
Create slash commands, context menus, and handle all interaction types with modern Discord bot functionality.
from interactions import slash_command, SlashContext
@slash_command(name="hello", description="Say hello to someone!")
async def hello_command(ctx: SlashContext):
await ctx.send("Hello World!")from interactions import slash_command, slash_option, SlashContext, OptionType
@slash_command(name="greet", description="Greet a user")
@slash_option(
name="user",
description="User to greet",
required=True,
opt_type=OptionType.USER
)
@slash_option(
name="message",
description="Custom message",
required=False,
opt_type=OptionType.STRING
)
async def greet_command(ctx: SlashContext, user: Member, message: str = "Hello"):
await ctx.send(f"{message}, {user.mention}!")from interactions import (
slash_command, slash_str_option, slash_int_option,
slash_bool_option, slash_user_option, slash_channel_option,
slash_role_option, slash_mentionable_option, slash_attachment_option,
slash_float_option, SlashContext
)
@slash_command(name="complex", description="Complex command with multiple option types")
@slash_str_option(name="text", description="Text input", required=True, max_length=100)
@slash_int_option(name="number", description="Number input", min_value=1, max_value=100)
@slash_bool_option(name="flag", description="Boolean flag")
@slash_user_option(name="target", description="Target user")
@slash_channel_option(name="destination", description="Target channel")
@slash_role_option(name="role", description="Role to assign")
@slash_attachment_option(name="file", description="File to process")
@slash_float_option(name="rating", description="Rating", min_value=0.0, max_value=5.0)
async def complex_command(
ctx: SlashContext,
text: str,
number: int = 50,
flag: bool = False,
target: Member = None,
destination: GuildChannel = None,
role: Role = None,
file: Attachment = None,
rating: float = 0.0
):
await ctx.send(f"Processing: {text} with number {number}")from interactions import slash_command, slash_option, SlashCommandChoice, SlashContext, OptionType
@slash_command(name="color", description="Choose a color")
@slash_option(
name="color_choice",
description="Pick your favorite color",
required=True,
opt_type=OptionType.STRING,
choices=[
SlashCommandChoice(name="Red", value="red"),
SlashCommandChoice(name="Blue", value="blue"),
SlashCommandChoice(name="Green", value="green"),
SlashCommandChoice(name="Yellow", value="yellow")
]
)
async def color_command(ctx: SlashContext, color_choice: str):
await ctx.send(f"You chose: {color_choice}")from interactions import SlashCommand, subcommand, SlashContext
# Create base command group
base_cmd = SlashCommand(name="admin", description="Admin commands", group_name="administration")
@subcommand(base=base_cmd, name="ban", description="Ban a user")
@slash_option(name="user", description="User to ban", required=True, opt_type=OptionType.USER)
@slash_option(name="reason", description="Ban reason", required=False, opt_type=OptionType.STRING)
async def admin_ban(ctx: SlashContext, user: Member, reason: str = "No reason provided"):
await user.ban(reason=reason)
await ctx.send(f"Banned {user.mention} for: {reason}")
@subcommand(base=base_cmd, name="kick", description="Kick a user")
@slash_option(name="user", description="User to kick", required=True, opt_type=OptionType.USER)
async def admin_kick(ctx: SlashContext, user: Member):
await user.kick()
await ctx.send(f"Kicked {user.mention}")from interactions import slash_command, slash_default_member_permission, Permissions
@slash_command(name="mod_only", description="Moderator only command")
@slash_default_member_permission(Permissions.MANAGE_MESSAGES)
async def mod_only_command(ctx: SlashContext):
await ctx.send("You have moderation permissions!")from interactions import context_menu, ContextMenuContext, CommandType
@context_menu(name="Get User Info", context_type=CommandType.USER)
async def user_info_menu(ctx: ContextMenuContext):
user = ctx.target # The user that was right-clicked
embed = Embed(
title=f"User Info: {user.username}",
description=f"ID: {user.id}\nCreated: {user.created_at}",
color=0x00ff00
)
await ctx.send(embed=embed, ephemeral=True)from interactions import context_menu, ContextMenuContext, CommandType
@context_menu(name="Quote Message", context_type=CommandType.MESSAGE)
async def quote_menu(ctx: ContextMenuContext):
message = ctx.target # The message that was right-clicked
quote_embed = Embed(
description=message.content,
color=0x0099ff
)
quote_embed.set_author(
name=message.author.username,
icon_url=message.author.avatar.url if message.author.avatar else None
)
await ctx.send(f"Quote from {message.author.mention}:", embed=quote_embed)from interactions import context_menu, CommandType, ContextMenuContext
@context_menu(name="Custom Action", context_type=CommandType.USER)
async def custom_menu(ctx: ContextMenuContext):
if ctx.context_type == CommandType.USER:
target = ctx.target
await ctx.send(f"Action performed on user: {target.username}")
elif ctx.context_type == CommandType.MESSAGE:
target = ctx.target
await ctx.send(f"Action performed on message from: {target.author.username}")class BaseContext:
bot: Client
author: Union[User, Member]
channel: MessageableChannel
guild: Optional[Guild]
message: Optional[Message]
created_at: datetimeclass InteractionContext(BaseContext):
interaction_id: Snowflake
token: str
application_id: Snowflake
type: InteractionType
data: dict
locale: str
guild_locale: Optional[str]
responded: bool
deferred: bool
ephemeral: boolKey Methods:
send(content=None, **kwargs) { .api } - Send response/followupdefer(ephemeral=False) { .api } - Defer the interactionedit(content=None, **kwargs) { .api } - Edit original responsedelete() { .api } - Delete original responsefollowup(content=None, **kwargs) { .api } - Send followup messageclass SlashContext(InteractionContext):
command: SlashCommand
args: List[Any]
kwargs: Dict[str, Any]
options: List[dict]
focused_option: Optional[str] # For autocompleteclass ComponentContext(InteractionContext):
component: BaseComponent
custom_id: str
component_type: ComponentType
values: List[str] # For select menusclass ModalContext(InteractionContext):
custom_id: str
components: List[dict]
values: Dict[str, str] # component_id -> value mappingclass ContextMenuContext(InteractionContext):
command_type: CommandType
target: Union[User, Member, Message]
target_id: SnowflakeProperties:
target_user { .api } - Target user (if USER command)target_message { .api } - Target message (if MESSAGE command)from interactions import slash_command, slash_option, AutocompleteContext, OptionType
@slash_command(name="search", description="Search for something")
@slash_option(
name="query",
description="Search query",
required=True,
opt_type=OptionType.STRING,
autocomplete=True
)
async def search_command(ctx: SlashContext, query: str):
await ctx.send(f"Searching for: {query}")
@search_command.autocomplete("query")
async def search_autocomplete(ctx: AutocompleteContext):
# Get user's current input
user_input = ctx.input_text
# Generate suggestions based on input
suggestions = [
{"name": f"Search: {user_input}", "value": user_input},
{"name": f"Advanced: {user_input}", "value": f"advanced:{user_input}"},
{"name": f"Exact: {user_input}", "value": f'"{user_input}"'}
]
# Return up to 25 suggestions
await ctx.send(suggestions[:25])from interactions import global_autocomplete, OptionType
@global_autocomplete("color")
async def color_autocomplete(ctx: AutocompleteContext):
"""Global autocomplete for any 'color' option"""
colors = ["red", "blue", "green", "yellow", "purple", "orange", "pink"]
user_input = ctx.input_text.lower()
# Filter colors based on user input
matching_colors = [c for c in colors if user_input in c.lower()]
suggestions = [{"name": color.title(), "value": color} for color in matching_colors]
await ctx.send(suggestions[:25])# Send simple message
await ctx.send("Hello World!")
# Send ephemeral message (only visible to user)
await ctx.send("Secret message!", ephemeral=True)
# Send with embed
embed = Embed(title="Title", description="Description", color=0x00ff00)
await ctx.send(embed=embed)
# Send with file
file = File("path/to/image.png")
await ctx.send("Here's an image:", file=file)from interactions import Button, ActionRow, ButtonStyle
# Send with components
button = Button(
style=ButtonStyle.PRIMARY,
label="Click Me!",
custom_id="my_button"
)
action_row = ActionRow(button)
await ctx.send("Click the button below:", components=[action_row])
# Send with multiple embeds
embed1 = Embed(title="First", description="First embed")
embed2 = Embed(title="Second", description="Second embed")
await ctx.send(embeds=[embed1, embed2])
# Send with allowed mentions control
from interactions import AllowedMentions
mentions = AllowedMentions(users=False, roles=False, everyone=False)
await ctx.send("@everyone This won't ping!", allowed_mentions=mentions)# Defer for long operations
@slash_command(name="slow", description="A slow command")
async def slow_command(ctx: SlashContext):
await ctx.defer() # Shows "Bot is thinking..."
# Do slow work here
await asyncio.sleep(5)
# Send the actual response
await ctx.send("Done with slow operation!")
# Defer ephemeral
@slash_command(name="secret_slow", description="A slow secret command")
async def secret_slow_command(ctx: SlashContext):
await ctx.defer(ephemeral=True)
# Do work...
await asyncio.sleep(3)
await ctx.send("Secret result!", ephemeral=True)@slash_command(name="countdown", description="Count down from 5")
async def countdown_command(ctx: SlashContext):
# Send initial response
await ctx.send("5...")
# Edit the response multiple times
for i in range(4, 0, -1):
await asyncio.sleep(1)
await ctx.edit(f"{i}...")
await asyncio.sleep(1)
await ctx.edit("🎉 Done!")@slash_command(name="multi", description="Send multiple messages")
async def multi_command(ctx: SlashContext):
# Initial response
await ctx.send("First message!")
# Additional messages via followup
await ctx.followup("Second message!")
await ctx.followup("Third message!", ephemeral=True)
# Followups return Message objects
msg = await ctx.followup("Fourth message - I can edit this!")
await asyncio.sleep(2)
await msg.edit("Edited the fourth message!")class SlashCommand:
name: str
description: str
options: List[SlashCommandOption]
callback: Callable
group_name: Optional[str]
sub_cmd_name: Optional[str]
dm_permission: bool
default_member_permissions: Optional[Permissions]
nsfw: boolclass ContextMenu:
name: str
type: CommandType
callback: Callable
dm_permission: bool
default_member_permissions: Optional[Permissions]
nsfw: boolclass SlashCommandOption:
name: str
description: str
type: OptionType
required: bool
choices: Optional[List[SlashCommandChoice]]
options: Optional[List[SlashCommandOption]] # For subcommands
channel_types: Optional[List[ChannelType]]
min_value: Optional[Union[int, float]]
max_value: Optional[Union[int, float]]
min_length: Optional[int]
max_length: Optional[int]
autocomplete: bool
class SlashCommandChoice:
name: str
value: Union[str, int, float]
name_localizations: Optional[Dict[str, str]]from interactions import OptionType
OptionType.STRING # Text input
OptionType.INTEGER # Whole number
OptionType.BOOLEAN # True/False
OptionType.USER # Discord user
OptionType.CHANNEL # Discord channel
OptionType.ROLE # Discord role
OptionType.MENTIONABLE # User or role
OptionType.NUMBER # Decimal number
OptionType.ATTACHMENT # File attachmentfrom interactions import LocalizedName, LocalizedDesc
@slash_command(
name=LocalizedName(default="hello", **{"es-ES": "hola", "fr": "bonjour"}),
description=LocalizedDesc(default="Say hello", **{"es-ES": "Di hola", "fr": "Dire bonjour"})
)
async def localized_command(ctx: SlashContext):
await ctx.send("Hello! / ¡Hola! / Bonjour!")# Check if commands need syncing
if interactions.sync_needed(bot.interactions):
await bot.sync_interactions()
# Convert commands to dict format
commands_dict = interactions.application_commands_to_dict(bot.interactions)# Create command group
admin_group = SlashCommand(
name="admin",
description="Admin commands",
group_name="administration"
)
# Commands with same base form a group
@subcommand(base=admin_group, name="users", description="Manage users")
async def admin_users(ctx: SlashContext):
pass
@subcommand(base=admin_group, name="channels", description="Manage channels")
async def admin_channels(ctx: SlashContext):
passInstall with Tessl CLI
npx tessl i tessl/pypi-discord-py-interactions