Модели и перечисления¶
Всё, что bostie декодирует из ответов Discord, — это модели: Message, Guild, Member, Interaction и ещё около двухсот, и все их можно импортировать из bostie. Модели — неизменяемые структуры msgspec, декодируемые прямо из байтов JSON.
import msgspec
import bostie
from bostie import (
Context,
Embed,
EmbedField,
Message,
MessageType,
WebhookMessage,
fmt,
)
bot = bostie.Bot()
@bot.message_command("Inspect")
async def inspect(ctx: Context, message: Message | WebhookMessage) -> None:
if isinstance(message, WebhookMessage):
sender = f"the webhook {message.author.username}"
else:
sender = message.author.mention
kind = message.type.name
if message.type.name.startswith("UNKNOWN_"):
kind = f"a type bostie does not know yet ({message.type.value})"
embed = Embed(
title="Message",
url=message.jump_url,
fields=(
EmbedField(name="Sent by", value=sender),
EmbedField(name="Type", value=kind),
EmbedField(name="Deletable", value=str(message.type.deletable)),
EmbedField(name="Created", value=fmt.timestamp(message.created_at)),
EmbedField(name="Raw keys", value=", ".join(sorted(message.raw or {}))),
),
)
if message.type is MessageType.REPLY:
embed = msgspec.structs.replace(embed, description="A reply.")
await ctx.respond(embeds=[embed], ephemeral=True)
Типы следуют документации Discord¶
Каждое поле типизировано так, как его описывает документация Discord:
- У поля, которое Discord присылает всегда, нет значения по умолчанию, и оно никогда не отсутствует. Если документация говорит, что оно может быть
null, его тип —T | None. - У поля, которое документация отмечает как необязательное, есть значение по умолчанию:
()для списка, пустой набор флагов для флагов,Falseдля логического поля, отсутствие которого означает «ложь», иNoneв остальных случаях. - ID — это
int, метки времени — объектыdatetimeв UTC с часовым поясом, а цвета —int. - Там, где Discord присылает частичный объект, у поля частичная модель:
PartialUser,PartialMember,PartialChannel,PartialMessage,PartialEmoji,PartialApplication,PartialIntegration,PartialEntitlement,UnavailableGuild. У частичной модели есть только поля частичной полезной нагрузки, поэтому обращение к отсутствующему полю — ошибка типов, а неNoneво время выполнения.
В трёх местах, где у одной полезной нагрузки две формы, есть два класса:
- Сообщение, отправленное вебхуком, включая ответы на взаимодействия, — это
WebhookMessage, у которогоauthor— этоPartialUser. Любое другое сообщение —Messageс полным авторомUser. Поля, которые принимают и то и другое, типизированы какMessage | WebhookMessage. - Участник, полученный из параметра, меню выбора или цели команды, — это
ResolvedMember, который несёт своегоuserи не имеетdeafиmute. УMemberиз шлюза или REST есть и то и другое. - Пользователь в
message.mentions— этоMentionedUser:Userсmember, которого Discord добавляет, если сообщение отправлено на сервере.
Взаимодействия — это CommandInteraction, ComponentInteraction, ModalSubmitInteraction или AutocompleteInteraction в зависимости от типа, у каждого из которых типизированное data.
Устойчивость к изменениям¶
Discord без предупреждения добавляет поля, значения перечислений и типы компонентов, и бот не должен от этого ломаться:
- Поля, которых bostie не знает, при декодировании игнорируются.
- Перечисления отображают неизвестное значение в стабильный член с именем
UNKNOWN_<value>, который равен этому значению:MessageType(99).name == "UNKNOWN_99". - Флаги сохраняют биты, которых bostie не знает, во всех операциях, включая
~. - Неизвестный тип компонента декодируется в
UnknownComponent, который хранит свой сырой JSON и отправляется обратно без изменений, если передать его вcomponents; неизвестный тип взаимодействия декодируется вUnknownInteraction, который доходит до слушателейInteractionCreate, но не до команд и маршрутов. - Вложенное сообщение, например
message.referenced_messageили сообщение взаимодействия с компонентом, декодируется только при чтении свойства, поэтому повреждённое сообщение даёт ошибку только там, где его читают.
Полезная нагрузка события, которая совсем не подходит к своему классу, например из-за отсутствия обязательного поля, один раз пишется в лог и доставляется только слушателям RawEvent, как объясняет раздел «События».
Сырые полезные нагрузки¶
Модель, декодированная из полной полезной нагрузки, хранит её: model.raw_json содержит байты, а model.raw декодирует их в новый dict. У вложенных моделей, например автора сообщения, raw_json = None. У каждого события тоже есть raw и raw_json. Через них доступно любое поле, которое Discord присылает раньше, чем bostie начнёт его моделировать.
repr модели не показывает raw_json и секреты: token, access_token, refresh_token и id_token.
Создание и копирование моделей¶
Модели неизменяемы и создаются с именованными аргументами. msgspec.structs.replace(model, field=value) возвращает копию с изменёнными полями. Модели, которые вы отправляете в Discord, например Embed, AllowedMentions, PollCreateRequest, MessageReference, ForumTag, RecurrenceRule и AutoModerationAction, не включают поля, которые вы не задали, поэтому декодированная модель отправляется обратно такой, какой пришла.
Вспомогательные свойства моделей¶
| Свойство | Значение |
|---|---|
user.mention, member.mention, role.mention, channel.mention |
текст упоминания, например <@123> |
user.display_name, member.display_name |
глобальное имя или имя пользователя; для участника — сначала никнейм |
message.jump_url |
ссылка на сообщение |
message.author_member |
автор как ResolvedMember, если сообщение несёт участника |
role.permissions, overwrite.allow, overwrite.deny |
флаги Permissions |
member.permissions |
права участника в канале взаимодействия, если Discord их прислал |
webhook.url |
URL вебхука, если у него есть токен |
Руководство по форматированию описывает упоминания, метки времени и URL в CDN, построенные из ID.
Snowflake-идентификаторы¶
ID в Discord кодируют момент создания объекта. У каждой модели со snowflake-идентификатором есть created_at — этот момент как datetime в UTC: message.created_at, member.created_at (момент создания аккаунта), emoji.created_at (None для эмодзи Unicode). bostie.snowflake_time(id) делает то же для голого ID, а bostie.time_snowflake(when) возвращает наименьший ID, созданный в заданный момент, или с high=True — наибольший, для постраничного перебора по дате. datetime без часового пояса считается местным временем.
Перечисления¶
bostie экспортирует перечисления и флаги Discord: ChannelType, MessageType, Permissions, Intents, MessageFlags, Locale, ButtonStyle и остальные из справочника по перечислениям. Флаги объединяются через | и проверяются через in: Permissions.SEND_MESSAGES in member_permissions. message.type.deletable говорит, позволяет ли Discord удалить сообщение этого типа: уведомления групповых ЛС и звонков, изменения названия и значка канала и стартовое сообщение ветки удалить нельзя. JSONErrorCode даёт имена кодам ошибок Discord в том виде, в каком их сообщает HTTPException.code.