ILLEST Developers

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.

Python
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

Python
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

Python
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

CallWhat 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.

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

Python
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

Python
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.