Перейти к содержанию

Модели и перечисления

Всё, что 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.