Pular para conteúdo

Conteúdo de mensagens

Uma mensagem é montada a partir das mesmas partes onde quer que você a envie: ctx.respond, ctx.followup, ctx.edit e ctx.update, bot.rest.messages.create e edit, e o execute de um webhook. Cada parte é um argumento nomeado, e uma parte que você deixa de fora não é enviada.

Parte Tipo Observações
content str o primeiro argumento posicional de respond, followup, messages.create e execute
embeds objetos Embed até 10 incorporações
components builders de ui botões, menus de seleção e os componentes de layout; veja Componentes
files objetos File uploads; veja abaixo
allowed_mentions AllowedMentions quais menções podem notificar; veja Menções permitidas
flags MessageFlags como SUPPRESS_EMBEDS e SUPPRESS_NOTIFICATIONS
tts bool lê o conteúdo em voz alta para quem estiver com o canal aberto
poll PollCreateRequest uma enquete; veja abaixo
ephemeral bool só em respostas a interações

bot.rest.messages.create também aceita reply_to, message_reference, sticker_ids, nonce, enforce_nonce e shared_client_theme, e delete_after, como as respostas a interações.

Incorporações e arquivos

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 e as suas partes (EmbedField, EmbedFooter, EmbedAuthor, EmbedMedia) são modelos imutáveis com argumentos nomeados; um dict simples no formato JSON do Discord também funciona. color aceita um int como 0x5865F2, e os helpers de bostie.color montam um.

File(name, data) envia data com o nome name. description é o texto alternativo, spoiler=True o desfoca e title define o título que o Discord mostra. Uma incorporação ou um componente se refere a um upload como attachment://name, como o gráfico faz acima. O bostie não verifica tamanhos: o limite de upload do Discord depende dos impulsos do servidor e do Nitro do usuário.

Enquetes

PollCreateRequest aceita uma question e answers, ambos PollMedia com text e, nas respostas, um emoji opcional. duration é em horas, 24 quando omitido, e allow_multiselect=True permite que um usuário escolha várias respostas. bot.rest.polls.end(channel_id, message_id) encerra uma enquete antes da hora, bot.rest.polls.list_answer_voters(...) lista quem escolheu uma resposta, e os eventos PollVoteAdd e PollVoteRemove informam os votos conforme acontecem.

Edição e anexos

Uma edição aceita as partes por palavra-chave, além de attachments. Uma parte que você deixa de fora permanece, e None a limpa: ctx.edit(embeds=None) remove as incorporações e mantém o conteúdo. Quando uma edição passa files ou attachments, a mensagem mantém só os anexos listados em attachments, dados como objetos Attachment ou IDs, mais os arquivos novos.

Components V2

Uma mensagem cujos components incluem um componente de layout (ui.text, ui.section, ui.container, ui.media_gallery, ui.file, ui.separator ou ui.thumbnail) é uma mensagem Components V2: o bostie define a flag IS_COMPONENTS_V2 e os componentes são a mensagem inteira. Uma mensagem assim não pode ter content, embeds, figurinhas nem enquete, e o bostie levanta ValueError quando você os passa; coloque o texto em ui.text. Botões e menus de seleção sozinhos em ui.row também funcionam em mensagens clássicas com conteúdo.

Respostas

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 aceita o ID de uma mensagem no mesmo canal. Para encaminhar uma mensagem, passe message_reference=MessageReference(type=MessageReferenceType.FORWARD, message_id=..., channel_id=...) no lugar disso; os dois não podem ser combinados. Ler message.content em servidores exige a intent privilegiada Message Content, como o guia do gateway explica.

Indicador de digitação

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(): mostra o bot digitando no canal da interação enquanto o bloco roda. O bostie envia o indicador quando o bloco começa e de novo a cada 9 segundos, já que o Discord o mostra por 10, e para quando o bloco termina, inclusive por uma exceção. await ctx.typing() o mostra uma vez. Fora de uma interação, bot.rest.channels.trigger_typing_indicator(channel_id) faz o mesmo, com await ou em async with. Uma mensagem que o bot envia no canal limpa o indicador na hora.

Mensagens 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) lê um caminho de arquivo ou bytes, mede a duração, calcula a forma de onda que o Discord desenha e retorna um File pronto para enviar. A extensão do nome indica o formato; os clientes do Discord gravam Ogg Opus mono a 48 kHz. Uma mensagem de voz é esse arquivo e nada mais: sem conteúdo, incorporações, componentes, enquete ou figurinhas. O bostie define a flag IS_VOICE_MESSAGE e levanta ValueError se houver qualquer outra coisa na mensagem. Um File que você mesmo monta vira uma mensagem de voz quando tem duration_secs e waveform, as amostras de 8 bits que o Discord desenha.