Interactions
Slash commands, context menus, buttons, select menus and forms. What sets them apart on ILLEST is how they travel: using any of them seals a message into the conversation that only the bot acts on, so the server never learns which command was used or with what.
Slash commands
Commands live on bot.tree. tree.sync() publishes them, and they appear in the app's / menu at once.
import illest
from illest import app_commands
from illest.ext import commands
bot = commands.Bot(command_prefix="!", intents=illest.Intents.default())
@bot.event
async def setup_hook():
await bot.tree.sync()
@bot.tree.command(description="Roll a die")
@app_commands.describe(sides="How many sides it has")
async def roll(interaction: illest.Interaction, sides: app_commands.Range[int, 2, 100] = 6):
import random
await interaction.response.send_message(f"{random.randint(1, sides)}")Options take their type from the annotation: str, int, float, bool, illest.Member, illest.Role, a channel type,
app_commands.Range for bounds, and app_commands.Choice or @app_commands.choices for a fixed list.
Groups and autocomplete
import illest
from illest import app_commands
tags = app_commands.Group(name="tag", description="Saved answers")
SAVED = {"rules": "Be kind.", "faq": "Read the pins first."}
async def tag_names(interaction: illest.Interaction, current: str):
return [app_commands.Choice(name=name, value=name) for name in SAVED if current.lower() in name][:25]
@tags.command(description="Post a saved answer")
@app_commands.autocomplete(name=tag_names)
async def show(interaction: illest.Interaction, name: str):
await interaction.response.send_message(SAVED.get(name, "No such tag."))Add the group with bot.tree.add_command(tags) before syncing.
Context menus
import illest
from illest.ext import commands
bot = commands.Bot(command_prefix="!", intents=illest.Intents.default())
@bot.tree.context_menu(name="Quote")
async def quote(interaction: illest.Interaction, message: illest.Message):
await interaction.response.send_message(f"> {message.content}\n- {message.author.display_name}")Answering
| Call | What it does |
|---|---|
interaction.response.send_message(...) | The answer. ephemeral=True shows it to the one person only. |
interaction.response.defer() | "Thinking" until interaction.followup.send(...) replaces it. |
interaction.response.edit_message(...) | For a button or menu: change the message it was on. |
interaction.response.send_modal(form) | Open a form. |
interaction.followup.send(...) | More messages, for 15 minutes. |
There is no interaction token and no three-second deadline. Answers and follow-ups are taken for 15 minutes.
Buttons and select menus
A view holds the components. A callback gets the interaction and the component.
import illest
from illest import ui
class Vote(ui.View):
def __init__(self):
super().__init__(timeout=60)
self.yes: set[int] = set()
@ui.button(label="Yes", style=illest.ButtonStyle.success)
async def vote_yes(self, interaction: illest.Interaction, button: ui.Button):
self.yes.add(interaction.user.id)
await interaction.response.edit_message(content=f"Yes {len(self.yes)}", view=self)
@ui.select(placeholder="Or pick one", options=[illest.SelectOption(label="Mint", value="mint")])
async def pick(self, interaction: illest.Interaction, select: ui.Select):
await interaction.response.send_message(f"{select.values[0]} it is.", ephemeral=True)Send it with await channel.send("Vote", view=Vote()). ui.UserSelect, ui.RoleSelect, ui.ChannelSelect and
ui.MentionableSelect pick from the server instead of a list.
Views that outlive a restart
Give the view timeout=None and every item a fixed custom_id, then register it again on every start:
import illest
from illest import ui
from illest.ext import commands
class RolePicker(ui.View):
def __init__(self):
super().__init__(timeout=None)
@ui.select(cls=ui.RoleSelect, custom_id="roles:pick", placeholder="Pick your roles", max_values=5)
async def pick(self, interaction: illest.Interaction, select: ui.RoleSelect):
await interaction.response.send_message("Saved.", ephemeral=True)
class Bot(commands.Bot):
async def setup_hook(self):
self.add_view(RolePicker())Forms
import illest
from illest import ui
class Feedback(ui.Modal, title="Tell us what you think"):
topic = ui.TextInput(label="Topic", max_length=80)
details = ui.TextInput(label="Details", style=illest.TextStyle.paragraph, required=False)
async def on_submit(self, interaction: illest.Interaction):
await interaction.response.send_message(f"Thanks, noted under {self.topic.value!r}.", ephemeral=True)Open it from a command or a button with await interaction.response.send_modal(Feedback()).
Who pressed it
interaction.user_verified is True when the person's own device vouched for the account. When it is False, interaction.user is
only the server's word: authorise nothing on it. See encryption.