Pular para conteúdo

Respondendo a interações

Todo comando de barra, comando de menu de contexto, botão, menu de seleção e envio de modal é uma interação, e o Discord espera o app respondê-la. Um handler de comando recebe um Context, um handler de componente um ComponentContext e um handler de modal um ModalContext; eles compartilham os métodos de resposta abaixo.

O que um contexto sabe

Atributo Valor
ctx.user o User que a disparou, em um servidor ou em uma mensagem direta
ctx.member o Member dele em um servidor; caso contrário, None
ctx.guild_id, ctx.channel_id onde aconteceu; guild_id é None em mensagens diretas
ctx.channel o PartialChannel que a interação traz
ctx.locale o idioma do cliente do usuário
ctx.interaction o modelo completo da interação, para todo o resto, como app_permissions ou guild_locale
ctx.bot o bot
ctx.voice a voz para este servidor e usuário
ctx.command_name em Context, o comando com os seus subcomandos, como "roles color set"

Responder, editar, acompanhar

import asyncio

import bostie
from bostie import Context

bot = bostie.Bot()


@bot.command
async def countdown(ctx: Context, seconds: int = 3) -> None:
    """Count down in one message."""
    await ctx.respond(f"{seconds}…")
    for left in range(seconds - 1, 0, -1):
        await asyncio.sleep(1)
        await ctx.edit(content=f"{left}…")
    await asyncio.sleep(1)
    await ctx.edit(content="Go!")
    note = await ctx.followup("Only you see this one.", ephemeral=True)
    await asyncio.sleep(5)
    await ctx.delete(note.id)

ctx.respond(...) envia a primeira resposta. Chamado de novo, ele envia uma mensagem de acompanhamento, então um handler pode chamá-lo quantas vezes precisar. Ele retorna a mensagem que o Discord criou, um WebhookMessage.

  • ctx.edit(**parts) edita a primeira resposta, e ctx.edit(message_id, **parts) uma mensagem de acompanhamento.
  • ctx.followup(...) sempre envia uma nova mensagem, exceto logo depois de defer(), quando ele preenche a resposta adiada.
  • ctx.fetch() e ctx.fetch(message_id) retornam a resposta ou uma mensagem de acompanhamento como o Discord as tem agora.
  • ctx.delete() e ctx.delete(message_id) as excluem.

respond e followup aceitam content e as partes listadas em Conteúdo de mensagens: embeds, components, files, allowed_mentions, flags, tts, poll, além de ephemeral. Uma edição aceita as mesmas partes por palavra-chave: uma parte que você deixa de fora fica como está, e None a limpa.

Mensagens que se excluem sozinhas

import bostie
from bostie import Context

bot = bostie.Bot()


@bot.command
async def nudge(ctx: Context) -> None:
    """Remind the channel to stay on topic."""
    await ctx.respond("Please keep this channel on topic.", delete_after=30)

delete_after= em respond, followup e bot.rest.messages.create exclui a mensagem enviada depois de tantos segundos. O bot executa a exclusão em segundo plano e a cancela quando fecha, então uma mensagem cujo prazo vence depois que o bot para continua lá. As mensagens de uma interação são excluídas pelo token dela, o que funciona também para as efêmeras, mas termina 15 minutos depois da interação; uma exclusão com prazo posterior falha com um aviso no log. Uma mensagem que alguém já excluiu é ignorada em silêncio.

Respostas efêmeras

ephemeral=True em uma chamada torna aquela mensagem visível só para o usuário. ephemeral=True em um comando, uma rota de componente, uma rota de modal ou um router torna isso o padrão para as respostas deles, e uma chamada ainda pode passar ephemeral=False. Uma mensagem existente não pode mudar entre efêmera e pública: uma edição que pede isso escreve um aviso no log uma vez e mantém a mensagem como está.

Adiando a resposta

A primeira resposta precisa chegar ao Discord em até 3 segundos. await ctx.defer() mostra "pensando…" no lugar dela, e o handler passa a ter 15 minutos para chamar respond; defer(ephemeral=True) torna efêmera a resposta que virá.

import asyncio

import bostie
from bostie import Context

bot = bostie.Bot(auto_defer=2.0)


@bot.command
async def report(ctx: Context) -> None:
    """Build a slow report."""
    await ctx.defer(ephemeral=True)
    await asyncio.sleep(10)
    await ctx.respond("The report is ready.")


@bot.command(auto_defer=None)
async def instant(ctx: Context) -> None:
    """Never deferred automatically."""
    await ctx.respond("Done.")

O bostie também adia sozinho: um uso ainda sem resposta auto_defer segundos depois de chegar tem a resposta adiada, 2,5 segundos por padrão. Um componente ou um modal aberto a partir de uma mensagem é adiado com defer_update, que mantém a mensagem como está até que update a edite. auto_defer é definido no bot, em um router, em um comando ou em uma rota, e None o desativa ali. O cronômetro conta a partir do momento em que o gateway entregou a interação, então um event loop bloqueado atrasa o handler, mas não o prazo; o bostie escreve um aviso no log quando um handler começa com mais de um segundo de atraso.

Modais e Atividades

await ctx.modal(ui.modal(...)) responde com um formulário em janela; veja Modais. await ctx.launch_activity() abre a Atividade do app e retorna o ID da instância dela. Os dois precisam ser a primeira resposta, e nada pode responder à interação depois de um modal: o envio do modal é uma nova interação.

As regras, e os erros por quebrá-las

Cada chamada verifica primeiro o estado da interação:

Chamada Antes de qualquer resposta Depois de defer() Depois de defer_update() Depois de uma resposta
respond responde preenche a resposta envia uma mensagem de acompanhamento envia uma mensagem de acompanhamento
defer adia nada nada InteractionResponded
edit, delete InteractionNotAnswered edita, exclui a resposta edita, exclui a mensagem edita, exclui a resposta
followup InteractionNotAnswered preenche a resposta envia uma mensagem de acompanhamento envia uma mensagem de acompanhamento
update edita a mensagem InteractionResponded edita a mensagem InteractionResponded
modal, launch_activity responde InteractionResponded InteractionResponded InteractionResponded

update e defer_update só existem nos contextos de componente e de modal. Toda chamada feita depois de um modal levanta InteractionResponded.

Um token de interação vive 15 minutos. Qualquer chamada depois disso, ou depois que o Discord disse que o token é inválido, levanta InteractionExpired sem enviar nada.

Um handler que retorna sem responder deixa o usuário com "O aplicativo não respondeu", e o bostie escreve no log um aviso que nomeia o handler. Um que adiou e nunca respondeu deixa "pensando…" até o token expirar, também com um aviso. Quando um handler levanta uma exceção antes de responder, o bostie responde "Something went wrong.", de forma efêmera a menos que o handler tenha adiado, a não ser que um hook de erro responda.

O Discord permite 5 mensagens de acompanhamento para uma interação de um app instalado só no usuário, e o bostie avisa na sexta.

Menções permitidas

Por padrão, uma resposta pode mencionar usuários e cargos, mas não @everyone nem @here: Bot(allowed_mentions=...) é AllowedMentions(parse=("users", "roles")). Um allowed_mentions passado com uma mensagem muda só os tipos de menção que ele define, então AllowedMentions(users=(123,)) limita as menções a usuários e deixa a configuração de cargos como está. AllowedMentions(parse=()) silencia todas as menções, e replied_user=False impede que uma resposta mencione o autor da mensagem à qual ela responde.