消息内容¶
无论从哪里发送,消息都由同样的部分构成:ctx.respond、ctx.followup、ctx.edit 和 ctx.update,bot.rest.messages.create 和 edit,以及 Webhook 的 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)都是使用关键字参数构造的不可变模型;按 Discord JSON 结构写的普通 dict 也可以。color 接受一个 int,例如 0x5865F2,也可以用 bostie.color 中的辅助函数构建。
File(name, data) 以 name 为文件名上传 data。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 Intent,网关指南中有详细说明。
正在输入提示¶
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) 的作用相同,可以直接 await,也可以用在 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 客户端录制的是 48 kHz 单声道 Ogg Opus。语音消息只能包含这一个文件,不能有其他任何东西:不能有文字内容、嵌入内容、组件、投票或贴纸。bostie 会设置 IS_VOICE_MESSAGE 标志,如果消息中还有其他内容则引发 ValueError。你自己构造的 File 只要带有 duration_secs 和 waveform(Discord 绘制的 8 位采样值),就会成为语音消息。