Aller au contenu

Contenu des messages

Un message se compose des mêmes parties, où que vous l'envoyiez : ctx.respond, ctx.followup, ctx.edit et ctx.update, bot.rest.messages.create et edit, et le execute d'un webhook. Chaque partie est un argument nommé, et une partie omise n'est pas envoyée.

Partie Type Remarques
content str le premier argument positionnel de respond, followup, messages.create et execute
embeds objets Embed jusqu'à 10 embeds enrichis
components constructeurs ui boutons, menus de sélection et composants de mise en page ; voir Composants
files objets File fichiers envoyés ; voir plus bas
allowed_mentions AllowedMentions les mentions qui peuvent notifier ; voir Mentions autorisées
flags MessageFlags comme SUPPRESS_EMBEDS et SUPPRESS_NOTIFICATIONS
tts bool lit le contenu à voix haute pour quiconque a le salon ouvert
poll PollCreateRequest un sondage ; voir plus bas
ephemeral bool réponses aux interactions uniquement

bot.rest.messages.create accepte aussi reply_to, message_reference, sticker_ids, nonce, enforce_nonce et shared_client_theme, ainsi que delete_after, comme les réponses aux interactions.

Embeds et fichiers

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 et ses parties (EmbedField, EmbedFooter, EmbedAuthor, EmbedMedia) sont des modèles figés à arguments nommés ; un simple dict au format JSON de Discord fonctionne aussi. color prend un int comme 0x5865F2, et les utilitaires de bostie.color en construisent un.

File(name, data) envoie data sous le nom name. description est son texte alternatif, spoiler=True le floute, et title définit le titre que Discord affiche. Un embed ou un composant désigne un fichier envoyé par attachment://name, comme le fait le graphique ci-dessus. bostie ne vérifie pas les tailles : la limite d'envoi de Discord dépend des boosts du serveur et du Nitro de l'utilisateur.

Sondages

PollCreateRequest prend une question et des answers, tous deux des PollMedia avec text et, pour les réponses, un emoji facultatif. duration s'exprime en heures, 24 si elle est omise, et allow_multiselect=True permet à un utilisateur de choisir plusieurs réponses. bot.rest.polls.end(channel_id, message_id) clôt un sondage avant l'heure, bot.rest.polls.list_answer_voters(...) liste qui a choisi une réponse, et les événements PollVoteAdd et PollVoteRemove signalent les votes au moment où ils ont lieu.

Modification et pièces jointes

Une modification prend les parties par mot-clé, plus attachments. Une partie omise reste en place, et None l'efface : ctx.edit(embeds=None) retire les embeds et garde le contenu. Quand une modification passe files ou attachments, le message ne garde que les pièces jointes listées dans attachments, données sous forme d'objets Attachment ou d'ids, plus les nouveaux fichiers.

Components V2

Un message dont les components contiennent un composant de mise en page (ui.text, ui.section, ui.container, ui.media_gallery, ui.file, ui.separator ou ui.thumbnail) est un message Components V2 : bostie active le flag IS_COMPONENTS_V2 et les composants forment le message entier. Un tel message ne peut avoir ni content, ni embeds, ni autocollants, ni sondage, et bostie lève ValueError si vous les passez ; mettez plutôt le texte dans ui.text. Les boutons et menus de sélection placés seuls dans ui.row fonctionnent aussi dans les messages classiques avec du contenu.

Réponses

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 prend l'id d'un message du même salon. Pour transférer un message, passez plutôt message_reference=MessageReference(type=MessageReferenceType.FORWARD, message_id=..., channel_id=...) ; les deux ne peuvent pas être combinés. Lire message.content dans les serveurs nécessite l'intent privilégié Message Content, comme l'explique le guide de la gateway.

Indicateur d'écriture

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(): montre le bot en train d'écrire dans le salon de l'interaction pendant que le bloc s'exécute. bostie envoie l'indicateur au début du bloc, puis de nouveau toutes les 9 secondes, puisque Discord l'affiche pendant 10, et s'arrête à la fin du bloc, y compris quand il se termine par une exception. await ctx.typing() l'affiche une seule fois. Hors d'une interaction, bot.rest.channels.trigger_typing_indicator(channel_id) fait de même, avec await ou dans async with. Un message que le bot envoie dans le salon efface aussitôt l'indicateur.

Messages vocaux

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) lit un chemin de fichier ou des octets, mesure la durée, calcule la forme d'onde que Discord dessine, et renvoie un File prêt à envoyer. L'extension du nom indique le format ; les clients de Discord enregistrent en Ogg Opus mono à 48 kHz. Un message vocal se limite à ce seul fichier : ni contenu, ni embeds, ni composants, ni sondage, ni autocollants. bostie active le flag IS_VOICE_MESSAGE, et lève ValueError si le message contient quoi que ce soit d'autre. Un File que vous construisez vous-même devient un message vocal quand il a duration_secs et waveform, les échantillons sur 8 bits que Discord dessine.