Nachrichteninhalt¶
Eine Nachricht besteht aus denselben Teilen, egal wohin du sie sendest: ctx.respond, ctx.followup, ctx.edit und ctx.update, bot.rest.messages.create und edit sowie das execute eines Webhooks. Jeder Teil ist ein Schlüsselwortargument, und ein Teil, den du weglässt, wird nicht gesendet.
| Teil | Typ | Hinweise |
|---|---|---|
content |
str |
das erste Positionsargument von respond, followup, messages.create und execute |
embeds |
Embed-Objekte |
bis zu 10 Rich Embeds |
components |
ui-Builder |
Buttons, Auswahlmenüs und die Layout-Komponenten; siehe Komponenten |
files |
File-Objekte |
Uploads; siehe unten |
allowed_mentions |
AllowedMentions |
welche Erwähnungen anpingen dürfen; siehe Erlaubte Erwähnungen |
flags |
MessageFlags |
etwa SUPPRESS_EMBEDS und SUPPRESS_NOTIFICATIONS |
tts |
bool |
den Inhalt jedem vorlesen, der den Kanal geöffnet hat |
poll |
PollCreateRequest |
eine Umfrage; siehe unten |
ephemeral |
bool |
nur bei Antworten auf Interaktionen |
bot.rest.messages.create nimmt außerdem reply_to, message_reference, sticker_ids, nonce, enforce_nonce und shared_client_theme, sowie delete_after, wie es auch Antworten auf Interaktionen tun.
Embeds und Dateien¶
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 und seine Teile (EmbedField, EmbedFooter, EmbedAuthor, EmbedMedia) sind eingefrorene Modelle mit Schlüsselwortargumenten; ein einfaches dict in Discords JSON-Form funktioniert ebenfalls. color nimmt einen int wie 0x5865F2, und die Helfer in bostie.color bauen einen.
File(name, data) lädt data unter name hoch. description ist der Alternativtext, spoiler=True macht die Datei unscharf, und title legt den Titel fest, den Discord anzeigt. Ein Embed oder eine Komponente verweist auf einen Upload als attachment://name, wie oben das Diagramm. bostie prüft keine Größen: Discords Upload-Limit hängt von den Boosts des Servers und vom Nitro des Benutzers ab.
Umfragen¶
PollCreateRequest nimmt eine question und answers, beide PollMedia mit text und bei Antworten einem optionalen emoji. duration ist in Stunden angegeben, 24, wenn weggelassen, und allow_multiselect=True lässt einen Benutzer mehrere Antworten wählen. bot.rest.polls.end(channel_id, message_id) beendet eine Umfrage vorzeitig, bot.rest.polls.list_answer_voters(...) listet, wer eine Antwort gewählt hat, und die Events PollVoteAdd und PollVoteRemove melden Stimmen, sobald sie abgegeben werden.
Bearbeiten und Anhänge¶
Eine Bearbeitung nimmt die Teile als Schlüsselwortargumente, dazu attachments. Ein Teil, den du weglässt, bleibt, und None leert ihn: ctx.edit(embeds=None) entfernt die Embeds und behält den Inhalt. Übergibt eine Bearbeitung files oder attachments, behält die Nachricht nur die Anhänge, die in attachments aufgeführt sind, als Attachment-Objekte oder IDs, plus die neuen Dateien.
Components V2¶
Eine Nachricht, deren components eine Layout-Komponente enthalten (ui.text, ui.section, ui.container, ui.media_gallery, ui.file, ui.separator oder ui.thumbnail), ist eine Components-V2-Nachricht: bostie setzt das Flag IS_COMPONENTS_V2, und die Komponenten sind die gesamte Nachricht. Eine solche Nachricht kann weder content noch embeds, Sticker oder eine Umfrage haben, und bostie löst ValueError aus, wenn du sie übergibst; schreib den Text stattdessen in ui.text. Buttons und Auswahlmenüs allein in ui.row funktionieren auch in klassischen Nachrichten mit Inhalt.
Antworten¶
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 nimmt die ID einer Nachricht im selben Kanal. Um eine Nachricht weiterzuleiten, übergib stattdessen message_reference=MessageReference(type=MessageReferenceType.FORWARD, message_id=..., channel_id=...); beides lässt sich nicht kombinieren. Um message.content auf Servern zu lesen, brauchst du den privilegierten Message Content Intent, wie die Gateway-Anleitung erklärt.
Tipp-Anzeige¶
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(): zeigt im Kanal der Interaktion an, dass der Bot schreibt, solange der Block läuft. bostie sendet die Anzeige, wenn der Block beginnt, und danach alle 9 Sekunden erneut, da Discord sie 10 Sekunden lang anzeigt, und hört auf, wenn der Block endet, auch durch eine Exception. await ctx.typing() zeigt sie einmal an. Außerhalb einer Interaktion tut bot.rest.channels.trigger_typing_indicator(channel_id) dasselbe, mit await oder in async with. Eine Nachricht, die der Bot im Kanal sendet, beendet die Anzeige sofort.
Sprachnachrichten¶
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) liest einen Dateipfad oder Bytes, misst die Dauer, berechnet die Wellenform, die Discord zeichnet, und gibt ein sendefertiges File zurück. Die Dateiendung des Namens bestimmt das Format; Discords Clients nehmen 48 kHz Mono Ogg Opus auf. Eine Sprachnachricht besteht nur aus dieser einen Datei und sonst nichts: kein Inhalt, keine Embeds, Komponenten, Umfrage oder Sticker. bostie setzt das Flag IS_VOICE_MESSAGE und löst ValueError aus, wenn noch etwas anderes in der Nachricht steht. Ein File, das du selbst baust, wird zur Sprachnachricht, wenn es duration_secs und waveform hat, die 8-Bit-Samples, die Discord zeichnet.