Содержимое сообщений¶
Сообщение собирается из одних и тех же частей, куда бы вы его ни отправляли: ctx.respond, ctx.followup, ctx.edit и ctx.update, bot.rest.messages.create и edit, а также execute вебхука. Каждая часть — именованный аргумент, а часть, которую вы не передали, не отправляется.
| Часть | Тип | Примечания |
|---|---|---|
content |
str |
первый позиционный аргумент respond, followup, messages.create и execute |
embeds |
объекты Embed |
до 10 эмбедов |
components |
конструкторы ui |
кнопки, меню выбора и компоненты разметки; см. «Компоненты» |
files |
объекты File |
загрузки; см. ниже |
allowed_mentions |
AllowedMentions |
какие упоминания могут пинговать; см. «Разрешённые упоминания» |
flags |
MessageFlags |
например SUPPRESS_EMBEDS и SUPPRESS_NOTIFICATIONS |
tts |
bool |
зачитать содержимое вслух всем, у кого открыт канал |
poll |
PollCreateRequest |
опрос; см. ниже |
ephemeral |
bool |
только для ответов на взаимодействия |
bot.rest.messages.create также принимает reply_to, message_reference, sticker_ids, nonce, enforce_nonce и shared_client_theme, а ещё delete_after, как и ответы на взаимодействия.
Эмбеды и файлы¶
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 и его части (EmbedField, EmbedFooter, EmbedAuthor, EmbedMedia) — неизменяемые модели с именованными аргументами; подходит и обычный dict в формате JSON Discord. color принимает int, например 0x5865F2, а собрать его помогают функции из bostie.color.
File(name, data) загружает data под именем name. description — его альтернативный текст, spoiler=True размывает его, а title задаёт заголовок, который показывает Discord. Эмбед или компонент ссылается на загрузку как attachment://name, как диаграмма в примере выше. bostie не проверяет размеры: лимит загрузки в Discord зависит от бустов сервера и Nitro пользователя.
Опросы¶
PollCreateRequest принимает question и answers — оба типа PollMedia с text и, для ответов, необязательным emoji. duration задаётся в часах, по умолчанию 24, а allow_multiselect=True позволяет пользователю выбрать несколько ответов. bot.rest.polls.end(channel_id, message_id) досрочно завершает опрос, bot.rest.polls.list_answer_voters(...) перечисляет, кто выбрал ответ, а события PollVoteAdd и PollVoteRemove сообщают о голосах по мере их появления.
Изменение и вложения¶
Изменение принимает части как именованные аргументы, а также attachments. Часть, которую вы не передали, остаётся, а None её очищает: ctx.edit(embeds=None) удаляет эмбеды и оставляет содержимое. Если изменение передаёт files или attachments, в сообщении остаются только вложения, перечисленные в attachments (объектами Attachment или ID), плюс новые файлы.
Components V2¶
Сообщение, в components которого есть компонент разметки (ui.text, ui.section, ui.container, ui.media_gallery, ui.file, ui.separator или ui.thumbnail), — это сообщение Components V2: bostie ставит флаг IS_COMPONENTS_V2, и компоненты составляют всё сообщение. У такого сообщения не может быть content, embeds, стикеров или опроса, и bostie вызывает ValueError, если вы их передаёте; поместите текст в ui.text. Кнопки и меню выбора, находящиеся только в ui.row, работают и в классических сообщениях с содержимым.
Ответы¶
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 принимает ID сообщения в том же канале. Чтобы переслать сообщение, передайте вместо этого message_reference=MessageReference(type=MessageReferenceType.FORWARD, message_id=..., channel_id=...); одновременно их использовать нельзя. Чтение message.content на серверах требует привилегированного интента Message Content, как объясняет руководство по шлюзу.
Индикатор набора текста¶
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(): показывает, что бот печатает в канале взаимодействия, пока выполняется блок. bostie отправляет индикатор в начале блока и затем каждые 9 секунд, поскольку Discord показывает его 10 секунд, и прекращает, когда блок заканчивается, в том числе из-за исключения. await ctx.typing() показывает его один раз. Вне взаимодействия то же самое делает bot.rest.channels.trigger_typing_indicator(channel_id) — при ожидании или в async with. Сообщение, которое бот отправляет в канал, сразу убирает индикатор.
Голосовые сообщения¶
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) читает путь к файлу или байты, измеряет длительность, вычисляет волновую форму, которую рисует Discord, и возвращает File, готовый к отправке. Формат определяется по расширению имени; клиенты Discord записывают моно Ogg Opus 48 кГц. Голосовое сообщение — это только этот файл и ничего больше: ни содержимого, ни эмбедов, ни компонентов, ни опроса, ни стикеров. bostie ставит флаг IS_VOICE_MESSAGE и вызывает ValueError, если в сообщении есть что-то ещё. File, который вы собираете сами, становится голосовым сообщением, если у него есть duration_secs и waveform — 8-битные отсчёты, которые рисует Discord.