Contenido de los mensajes¶
Un mensaje se construye con las mismas partes dondequiera que lo envíes: ctx.respond, ctx.followup, ctx.edit y ctx.update, bot.rest.messages.create y edit, y el execute de un webhook. Cada parte es un argumento por palabra clave, y una parte que no pasas no se envía.
| Parte | Tipo | Notas |
|---|---|---|
content |
str |
el primer argumento posicional de respond, followup, messages.create y execute |
embeds |
objetos Embed |
hasta 10 embeds enriquecidos |
components |
builders de ui |
botones, menús de selección y los componentes de diseño; ver Componentes |
files |
objetos File |
archivos subidos; ver más abajo |
allowed_mentions |
AllowedMentions |
qué menciones pueden notificar; ver Menciones permitidas |
flags |
MessageFlags |
como SUPPRESS_EMBEDS y SUPPRESS_NOTIFICATIONS |
tts |
bool |
lee el contenido en voz alta a quien tenga el canal abierto |
poll |
PollCreateRequest |
una encuesta; ver más abajo |
ephemeral |
bool |
solo en respuestas a interacciones |
bot.rest.messages.create también acepta reply_to, message_reference, sticker_ids, nonce, enforce_nonce y shared_client_theme, y delete_after, igual que las respuestas a interacciones.
Embeds y archivos¶
import asyncio
from datetime import UTC, datetime
from pathlib import Path
import bostie
from bostie import (
AllowedMentions,
Context,
Embed,
EmbedField,
EmbedFooter,
EmbedMedia,
File,
PollAnswer,
PollCreateRequest,
PollMedia,
color,
)
bot = bostie.Bot()
@bot.command
async def status(ctx: Context) -> None:
"""Post a status report with a chart."""
data = await asyncio.to_thread(Path("chart.png").read_bytes)
chart = File("chart.png", data, description="Latency")
embed = Embed(
title="Status",
description="All systems normal.",
color=color.GREEN,
timestamp=datetime.now(UTC),
fields=(
EmbedField(name="Gateway", value="41 ms", inline=True),
EmbedField(name="Voice", value="23 ms", inline=True),
),
image=EmbedMedia(url="attachment://chart.png"),
footer=EmbedFooter(text="Updated every minute"),
)
await ctx.respond(embeds=[embed], files=[chart])
@bot.command
async def lunch(ctx: Context) -> None:
"""Ask where to eat."""
poll = PollCreateRequest(
question=PollMedia(text="Where do we eat?"),
answers=(
PollAnswer(poll_media=PollMedia(text="Pizza")),
PollAnswer(poll_media=PollMedia(text="Ramen")),
),
duration=2,
)
await ctx.respond(poll=poll)
@bot.command
async def shout(ctx: Context, text: str) -> None:
"""Repeat the text, letting @everyone ping."""
await ctx.respond(text, allowed_mentions=AllowedMentions(parse=("everyone",)))
Embed y sus partes (EmbedField, EmbedFooter, EmbedAuthor, EmbedMedia) son modelos inmutables con argumentos por palabra clave; un dict simple con la forma del JSON de Discord también funciona. color acepta un int como 0x5865F2, y las funciones auxiliares de bostie.color construyen uno.
File(name, data) sube data con el nombre name. description es su texto alternativo, spoiler=True lo difumina y title define el título que muestra Discord. Un embed o un componente hace referencia a un archivo subido como attachment://name, como hace el gráfico de arriba. bostie no comprueba los tamaños: el límite de subida de Discord depende de las mejoras del servidor y del Nitro del usuario.
Encuestas¶
PollCreateRequest acepta una question y answers, ambas PollMedia con text y, en las respuestas, un emoji opcional. duration está en horas, 24 si no se indica, y allow_multiselect=True permite que un usuario elija varias respuestas. bot.rest.polls.end(channel_id, message_id) cierra una encuesta antes de tiempo, bot.rest.polls.list_answer_voters(...) enumera quién eligió una respuesta, y los eventos PollVoteAdd y PollVoteRemove informan los votos a medida que ocurren.
Edición y archivos adjuntos¶
Una edición acepta las partes por palabra clave, además de attachments. Una parte que no pasas se queda, y None la borra: ctx.edit(embeds=None) quita los embeds y conserva el contenido. Cuando una edición pasa files o attachments, el mensaje conserva solo los archivos adjuntos que aparecen en attachments, dados como objetos Attachment o ids, además de los archivos nuevos.
Components V2¶
Un mensaje cuyos components incluyen un componente de diseño (ui.text, ui.section, ui.container, ui.media_gallery, ui.file, ui.separator o ui.thumbnail) es un mensaje de Components V2: bostie activa el flag IS_COMPONENTS_V2 y los componentes son el mensaje completo. Un mensaje así no puede tener content, embeds, stickers ni una encuesta, y bostie lanza ValueError cuando los pasas; pon el texto en ui.text. Los botones y menús de selección solos dentro de ui.row también funcionan en los mensajes clásicos con contenido.
Respuestas¶
import bostie
from bostie import Intents, MessageFlags
from bostie.events import MessageCreate
bot = bostie.Bot(intents=Intents.DEFAULT | Intents.MESSAGE_CONTENT)
@bot.listen
async def thank(event: MessageCreate) -> None:
message = event.message
if message.author.bot or "thanks" not in message.content.lower():
return
await bot.rest.messages.create(
message.channel_id,
"You're welcome!",
reply_to=message.id,
flags=MessageFlags.SUPPRESS_NOTIFICATIONS,
)
reply_to acepta el id de un mensaje del mismo canal. Para reenviar un mensaje, pasa en su lugar message_reference=MessageReference(type=MessageReferenceType.FORWARD, message_id=..., channel_id=...); los dos no se pueden combinar. Leer message.content en servidores requiere el intent privilegiado Message Content, como explica la guía del gateway.
Indicador de escritura¶
import asyncio
import bostie
from bostie import Context
bot = bostie.Bot()
def weekly_summary() -> str:
return "12 new members and 340 messages this week."
@bot.command
async def report(ctx: Context) -> None:
"""Write this week's report."""
await ctx.respond("Writing the report.")
async with ctx.typing():
summary = await asyncio.to_thread(weekly_summary)
await ctx.followup(summary)
async with ctx.typing(): muestra que el bot está escribiendo en el canal de la interacción mientras se ejecuta el bloque. bostie envía el indicador cuando empieza el bloque y de nuevo cada 9 segundos, porque Discord lo muestra durante 10, y se detiene cuando termina el bloque, también si termina por una excepción. await ctx.typing() lo muestra una vez. Fuera de una interacción, bot.rest.channels.trigger_typing_indicator(channel_id) hace lo mismo, con await o en async with. Un mensaje que el bot envía en el canal quita el indicador de inmediato.
Mensajes de voz¶
import bostie
from bostie import Context, voice
bot = bostie.Bot()
@bot.command
async def memo(ctx: Context) -> None:
"""Send today's memo as a voice message."""
await ctx.respond(files=[await voice.message("memo.ogg")])
await voice.message(audio) lee una ruta de archivo o bytes, mide la duración, calcula la forma de onda que dibuja Discord y devuelve un File listo para enviar. La extensión del nombre indica el formato; los clientes de Discord graban Ogg Opus mono a 48 kHz. Un mensaje de voz es ese único archivo y nada más: sin contenido, embeds, componentes, encuesta ni stickers. bostie activa el flag IS_VOICE_MESSAGE y lanza ValueError si hay cualquier otra cosa en el mensaje. Un File que construyes tú se convierte en mensaje de voz cuando tiene duration_secs y waveform, las muestras de 8 bits que dibuja Discord.