Skip to content

Views

A view is a whole message. You describe it once in render(), and risa turns it into the components Discord draws.

A view with no fields

The counter in Your first view kept a number. This welcome message has no fields, so it has nothing to remember. It draws a box with two lines of text and a button, and the button answers with a message only the person who clicked can see.

@risa.register(name="welcome")
class Welcome(risa.View):
    def render(self) -> ui.Layout:
        return ui.Container(
            ui.TextDisplay("## Welcome to the server"),
            ui.TextDisplay("Read the rules, then say hello in **#general**."),
            ui.Row(ui.Button(self.rules, label="Show the rules")),
            accent_color=0xF680A1,
        )

    @risa.handler
    async def rules(self, ctx: risa.ComponentContext) -> None:
        await ctx.respond("Be kind, and stay on topic.", ephemeral=True)

In Discord

Risa BotAPPToday at 12:00

Welcome to the server

Read the rules, then say hello in #general.

Show the rules

After a click

Risa BotAPPToday at 12:00

Be kind, and stay on topic.

Only you can see this·Dismiss message

From render() to Discord

render() returns one component or a sequence of them, and Discord draws them in that order.

In render() In Discord
ui.Container(...) A box around the components inside it, like an embed
accent_color=0xF680A1 The colored bar down the left side of the box
ui.TextDisplay("...") Text, in Discord's markdown
ui.Row(...) A row of buttons, or one select menu
ui.Button(self.rules, label="...") A button, and self.rules is the handler its clicks run

Selects, sections, galleries shows the other components.

A view is the whole message

A container or a text display makes the message a Components V2 message, and Discord's documentation says such a message has no content and no embeds. Text goes in ui.TextDisplay, so render() draws everything the message shows.

Register a view

@risa.register makes the class a view and gives it a name. A click finds its view by that name, so keep the name once messages are out. client.add_view tells a client to answer for the view, once at startup.

client = risa.client_from_app(bot)
client.add_view(Welcome)

Without add_view the message still sends, but its clicks fail in Discord, and client.build logs a warning once. A client made with risa.client_from_app(bot, use_global=True) needs no add_view: it answers every view whose module has been imported.

Send it

client.build renders a view and returns the result, ready to send. How you send it depends on where your code runs:

Your code runs in You send with
An event, such as a new message await rendered.send_to(bot.rest, event.channel_id)
A lightbulb command or a risa handler await ctx.respond(**rendered)
A raw hikari interaction with no answer yet, such as a slash command await rendered.respond_to(bot.rest, interaction)

Leave content out of a call that spreads a Components V2 view. Rows alone are the one exception.

The example uses the first row: writing !welcome in a server channel posts the message. As with !counter in Your first view, the bot needs Message Content Intent turned on in the Developer Portal.

@bot.listen()
async def on_message(event: hikari.GuildMessageCreateEvent) -> None:
    if event.content == "!welcome":
        rendered = await client.build(Welcome())
        await rendered.send_to(bot.rest, event.channel_id)
Full file
"""A view with no state: the whole message, and one button that answers."""

from __future__ import annotations

import os

import hikari

import risa
from risa import ui


@risa.register(name="welcome")
class Welcome(risa.View):
    def render(self) -> ui.Layout:
        return ui.Container(
            ui.TextDisplay("## Welcome to the server"),
            ui.TextDisplay("Read the rules, then say hello in **#general**."),
            ui.Row(ui.Button(self.rules, label="Show the rules")),
            accent_color=0xF680A1,
        )

    @risa.handler
    async def rules(self, ctx: risa.ComponentContext) -> None:
        await ctx.respond("Be kind, and stay on topic.", ephemeral=True)


def main() -> None:
    bot = hikari.GatewayBot(
        os.environ["DISCORD_TOKEN"],
        intents=hikari.Intents.ALL_UNPRIVILEGED | hikari.Intents.MESSAGE_CONTENT,
    )

    client = risa.client_from_app(bot)
    client.add_view(Welcome)

    @bot.listen()
    async def on_message(event: hikari.GuildMessageCreateEvent) -> None:
        if event.content == "!welcome":
            rendered = await client.build(Welcome())
            await rendered.send_to(bot.rest, event.channel_id)

    bot.run()


if __name__ == "__main__":
    main()