Saltar a contenido

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.