Part 6 - Message Components#

Message components are a relatively new feature on Discord, allowing you to attach buttons and select menus to messages!

Let’s add some new code to fun.py.

At the very top of the file, import asyncio:

import asyncio

Then, insert the following after the meme command, but above the load function:

 1ANIMALS = {
 2    "Dog": "🐶",
 3    "Cat": "🐱",
 4    "Panda": "🐼",
 5    "Fox": "🦊",
 6    "Red Panda": "🐼",
 7    "Koala": "🐨",
 8    "Bird": "🐦",
 9    "Racoon": "🦝",
10    "Kangaroo": "🦘",
11}
12
13
14@fun_group.child
15@lightbulb.command("animal", "Get a fact + picture of a cute animal :3")
16@lightbulb.implements(lightbulb.PrefixSubCommand, lightbulb.SlashSubCommand)
17async def animal_subcommand(ctx: lightbulb.Context) -> None:
18    select_menu = (
19        ctx.bot.rest.build_action_row()
20        .add_select_menu("animal_select")
21        .set_placeholder("Pick an animal")
22    )
23
24    for name, emoji in ANIMALS.items():
25        select_menu.add_option(
26            name,  # the label, which users see
27            name.lower().replace(" ", "_"),  # the value, which is used by us later
28        ).set_emoji(emoji).add_to_menu()
29
30    resp = await ctx.respond(
31        "Pick an animal from the dropdown :3",
32        component=select_menu.add_to_container(),
33    )
34    msg = await resp.message()
35
36    try:
37        event = await ctx.bot.wait_for(
38            hikari.InteractionCreateEvent,
39            timeout=60,
40            predicate=lambda e: isinstance(e.interaction, hikari.ComponentInteraction)
41            and e.interaction.user.id == ctx.author.id
42            and e.interaction.message.id == msg.id
43            and e.interaction.component_type == hikari.ComponentType.SELECT_MENU,
44        )
45    except asyncio.TimeoutError:
46        await msg.edit("The menu timed out :c", components=[])
47    else:
48        animal = event.interaction.values[0]
49        async with ctx.bot.d.aio_session.get(
50            f"https://some-random-api.ml/animal/{animal}"
51        ) as res:
52            if res.ok:
53                res = await res.json()
54                embed = hikari.Embed(description=res["fact"], colour=0x3B9DFF)
55                embed.set_image(res["image"])
56
57                animal = animal.replace("_", " ")
58
59                await msg.edit(
60                    f"Here's a {animal} for you! :3", embed=embed, components=[]
61                )
62            else:
63                await msg.edit(f"API returned a {res.status} status :c", components=[])
  • Line 1-11 - Create a dict containing all the possible endpoints of some-random-api.ml/animal/

  • Line 14-16 - Set up prefix and slash subcommands

  • Line 18-22
    • Create an action row, which returns an ActionRowBuilder

    • Add a select menu to the action row, with “animal_select” as the custom ID

    • Set the placeholder (the text that is seen when no option has been picked) to Pick an animal

  • Line 24-28 - For all the items in the ANIMALS dict, add an option to the select menu (Read the docs - SelectMenuBuilder.add_option) with
    • The name

    • The value, which is the name of the animal but lowercased and with spaces replaced with underscores

    • Setting the emoji to the value of the animal in the ANIMALS dict

  • Line 30-34
  • Line 36-44 - Wait for an interaction to be created and
    • Check if the interaction is a component interaction

    • Check that the interaction user is the same who ran the command

    • Check that the interaction message is the same as the message we sent

    • Check that the interaction component type is a select menu

  • Line 45-46 - If the interaction times out, an asyncio.TimeoutError will be raised, and so we can use that to handle the timeout by editing the message and removing the components

  • Line 48 - Get the value of the interaction (the selected option) - Read the docs - ComponentInteraction.values

  • Line 49-51 - Make a GET request to some-random-api.ml with the selected animal as the option

  • Line 52 - If the response has an ok status, then
    • Line 53 - Get the response’s json

    • Line 54 - Create an embed, setting its title to the animal fact

    • Line 55 - Set the embed’s image to the animal image

    • Line 57 - Replace the underscore in animal with a space

    • Line 59-61 - Edit the message to contain the embed, and remove the select menu component

  • Line 62 - Otherwise, if the response was not successful, then
    • Line 63 - Edit the message to say what status code the API responded with, and remove the select menu component

_static/animal_1.png _static/animal_2.png _static/animal_3.png

And if the menu times out:

_static/animal_4.png

Read the docs - Components