CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-discord-py-interactions

A Feature-rich Discord Bot Framework for Python with comprehensive API coverage and modern interfaces

Pending
Overview
Eval results
Files

commands.mddocs/

Commands & Interactions

Create slash commands, context menus, and handle all interaction types with modern Discord bot functionality.

Slash Commands

Basic Slash Command

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!")

Slash Command with Options

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}!")

Type-Specific Option Decorators

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}")

Slash Command Choices

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}")

Subcommands

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}")

Slash Command Permissions

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!")

Context Menu Commands

User Context Menu

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)

Message Context Menu

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)

Generic Context Menu

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}")

Command Context Classes

Base Context

class BaseContext:
    bot: Client
    author: Union[User, Member]
    channel: MessageableChannel
    guild: Optional[Guild]
    message: Optional[Message]
    created_at: datetime

Interaction Context

class 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: bool

Key Methods:

  • send(content=None, **kwargs) { .api } - Send response/followup
  • defer(ephemeral=False) { .api } - Defer the interaction
  • edit(content=None, **kwargs) { .api } - Edit original response
  • delete() { .api } - Delete original response
  • followup(content=None, **kwargs) { .api } - Send followup message

Slash Context

class SlashContext(InteractionContext):
    command: SlashCommand
    args: List[Any]
    kwargs: Dict[str, Any]
    options: List[dict]
    focused_option: Optional[str]  # For autocomplete

Component Context

class ComponentContext(InteractionContext):
    component: BaseComponent
    custom_id: str
    component_type: ComponentType
    values: List[str]  # For select menus

Modal Context

class ModalContext(InteractionContext):
    custom_id: str
    components: List[dict]
    values: Dict[str, str]  # component_id -> value mapping

Context Menu Context

class ContextMenuContext(InteractionContext):
    command_type: CommandType
    target: Union[User, Member, Message]
    target_id: Snowflake

Properties:

  • target_user { .api } - Target user (if USER command)
  • target_message { .api } - Target message (if MESSAGE command)

Autocomplete

Basic Autocomplete

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])

Global Autocomplete

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])

Response Types

Basic Responses

# 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)

Advanced Responses

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)

Deferred Responses

# 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)

Edit Responses

@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!")

Followup Messages

@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!")

Command Classes

Slash Command Class

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: bool

Context Menu Class

class ContextMenu:
    name: str
    type: CommandType
    callback: Callable
    dm_permission: bool
    default_member_permissions: Optional[Permissions]
    nsfw: bool

Command Options

class 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]]

Option Types

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 attachment

Advanced Features

Command Localization

from 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!")

Command Synchronization

# 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)

Command Groups

# 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):
    pass

Install with Tessl CLI

npx tessl i tessl/pypi-discord-py-interactions

docs

client.md

commands.md

components.md

discord-models.md

events.md

extensions.md

index.md

tile.json