Responder a interacciones¶
Cada comando slash, comando de menú contextual, botón, menú de selección y envío de modal es una interacción, y Discord espera que la aplicación la responda. Un handler de comando recibe un Context, un handler de componente un ComponentContext y un handler de modal un ModalContext; todos comparten los métodos para responder que se describen abajo.
Lo que sabe un contexto¶
| Atributo | Valor |
|---|---|
ctx.user |
el User que la activó, en un servidor o en un MD |
ctx.member |
su Member en un servidor; si no, None |
ctx.guild_id, ctx.channel_id |
dónde ocurrió; guild_id es None en los MD |
ctx.channel |
el PartialChannel que trae la interacción |
ctx.locale |
el idioma del cliente del usuario |
ctx.interaction |
el modelo completo de la interacción, para todo lo demás, como app_permissions o guild_locale |
ctx.bot |
el bot |
ctx.voice |
la voz para este servidor y este usuario |
ctx.command_name |
en Context, el comando con sus subcomandos, como "roles color set" |
Responder, editar, dar seguimiento¶
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(...) envía la primera respuesta. Si se vuelve a llamar, envía un mensaje de seguimiento, así que un handler puede llamarlo tantas veces como necesite. Devuelve el mensaje que creó Discord, un WebhookMessage.
ctx.edit(**parts)edita la primera respuesta, yctx.edit(message_id, **parts), un mensaje de seguimiento.ctx.followup(...)siempre envía un mensaje nuevo, salvo justo después dedefer(), donde completa la respuesta diferida.ctx.fetch()yctx.fetch(message_id)devuelven la respuesta o un mensaje de seguimiento tal como los tiene Discord ahora.ctx.delete()yctx.delete(message_id)los eliminan.
respond y followup aceptan content y las partes que se enumeran en Contenido de los mensajes: embeds, components, files, allowed_mentions, flags, tts, poll, además de ephemeral. Una edición acepta las mismas partes por palabra clave: una parte que no pasas queda como está, y None la borra.
Mensajes que se eliminan solos¶
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= en respond, followup y bot.rest.messages.create elimina el mensaje que envió esa cantidad de segundos después. El bot ejecuta la eliminación en segundo plano y la cancela cuando se cierra, así que un mensaje cuyo plazo vence después de que el bot se detiene se queda. Los mensajes de una interacción se eliminan a través de su token, lo que también funciona con los efímeros, pero deja de funcionar 15 minutos después de la interacción; una eliminación que vence más tarde falla y deja una advertencia en el log. Un mensaje que alguien ya eliminó se omite sin avisar.
Respuestas efímeras¶
ephemeral=True en una llamada hace que ese mensaje solo lo vea el usuario. ephemeral=True en un comando, una ruta de componente, una ruta de modal o un router lo vuelve el valor por defecto para sus respuestas, y una llamada todavía puede pasar ephemeral=False. Un mensaje existente no puede pasar de efímero a público ni al revés: una edición que lo pide deja una advertencia en el log una sola vez y mantiene el mensaje como está.
Diferir¶
La primera respuesta tiene que llegar a Discord en 3 segundos. await ctx.defer() muestra en su lugar que el bot «está pensando…», y el handler tiene entonces 15 minutos para llamar a respond; defer(ephemeral=True) hace que la respuesta que viene sea efímera.
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.")
bostie también difiere por su cuenta: un uso que sigue sin respuesta auto_defer segundos después de llegar se difiere, 2.5 segundos por defecto. Un componente o un modal abierto desde un mensaje se difiere con defer_update, que mantiene el mensaje como está hasta que update lo edita. auto_defer se define en el bot, un router, un comando o una ruta, y None lo desactiva ahí. El temporizador cuenta desde que el gateway entregó la interacción, así que un event loop bloqueado retrasa el handler pero no el plazo; bostie deja una advertencia en el log cuando un handler empieza con más de un segundo de retraso.
Modales y actividades¶
await ctx.modal(ui.modal(...)) responde con un formulario emergente; ver Modales. await ctx.launch_activity() abre la actividad de la aplicación y devuelve el id de su instancia. Los dos tienen que ser la primera respuesta, y nada puede responder la interacción después de un modal: el envío del modal es una interacción nueva.
Las reglas, y los errores por romperlas¶
Cada llamada comprueba primero el estado de la interacción:
| Llamada | Antes de cualquier respuesta | Después de defer() |
Después de defer_update() |
Después de una respuesta |
|---|---|---|---|---|
respond |
responde | completa la respuesta | envía un mensaje de seguimiento | envía un mensaje de seguimiento |
defer |
difiere | nada | nada | InteractionResponded |
edit, delete |
InteractionNotAnswered |
edita o elimina la respuesta | edita o elimina el mensaje | edita o elimina la respuesta |
followup |
InteractionNotAnswered |
completa la respuesta | envía un mensaje de seguimiento | envía un mensaje de seguimiento |
update |
edita el mensaje | InteractionResponded |
edita el mensaje | InteractionResponded |
modal, launch_activity |
responde | InteractionResponded |
InteractionResponded |
InteractionResponded |
update y defer_update solo existen en los contextos de componentes y de modales. Cada llamada que se hace después de un modal lanza InteractionResponded.
El token de una interacción dura 15 minutos. Cualquier llamada después de ese plazo, o después de que Discord dijo que el token no es válido, lanza InteractionExpired sin enviar nada.
Un handler que termina sin responder le deja al usuario el aviso de que la aplicación no respondió, y bostie deja en el log una advertencia con el nombre del handler. Uno que difirió y nunca respondió deja el «está pensando…» hasta que vence el token, también con una advertencia. Cuando un handler lanza una excepción antes de responder, bostie responde "Something went wrong.", de forma efímera salvo que el handler haya diferido, a menos que responda un hook de errores.
Discord permite 5 mensajes de seguimiento para una interacción de una aplicación instalada solo en la cuenta del usuario, y bostie advierte en el sexto.
Menciones permitidas¶
Por defecto, una respuesta puede mencionar usuarios y roles, pero no @everyone ni @here: Bot(allowed_mentions=...) es AllowedMentions(parse=("users", "roles")). Un allowed_mentions pasado con un mensaje cambia solo los tipos de mención que define, así que AllowedMentions(users=(123,)) limita las menciones de usuarios y deja como está el ajuste de los roles. AllowedMentions(parse=()) silencia todas las menciones, y replied_user=False evita que una respuesta mencione al autor del mensaje al que responde.