Modelos e enums¶
Tudo o que o bostie decodifica do Discord é um modelo: Message, Guild, Member, Interaction e cerca de duzentos outros, todos importáveis de bostie. Os modelos são structs msgspec imutáveis, decodificados direto dos bytes do 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)
Os tipos seguem a documentação do Discord¶
Cada campo é tipado como a documentação do Discord o descreve:
- Um campo que o Discord sempre envia não tem valor padrão e nunca falta. Se a documentação diz que ele pode ser
null, ele éT | None. - Um campo que a documentação marca como opcional tem um valor padrão:
()para uma lista, um conjunto de flags vazio para flags,Falsepara um booleano cuja ausência significa falso eNonenos outros casos. - IDs são
int, timestamps são objetosdatetimecom fuso horário em UTC e cores sãoint. - Onde o Discord envia um objeto parcial, o campo tem um modelo parcial:
PartialUser,PartialMember,PartialChannel,PartialMessage,PartialEmoji,PartialApplication,PartialIntegration,PartialEntitlement,UnavailableGuild. Um modelo parcial só tem os campos que o payload parcial tem, então um campo ausente é um erro de tipo, e não umNoneem tempo de execução.
Três lugares em que um payload tem dois formatos ganham duas classes:
- Uma mensagem enviada por um webhook, incluindo respostas a interações, é uma
WebhookMessage, cujoauthoré umPartialUser. Qualquer outra mensagem é umaMessagecom um autorUsercompleto. Os campos que aceitam as duas são tipados comoMessage | WebhookMessage. - Um membro resolvido a partir de uma opção, de um menu de seleção ou do alvo de um comando é um
ResolvedMember, que traz o seuusere não temdeafnemmute. UmMembervindo do gateway ou do REST tem os dois. - Um usuário em
message.mentionsé umMentionedUser: umUsercom omemberque o Discord adiciona quando a mensagem foi enviada em um servidor.
As interações são CommandInteraction, ComponentInteraction, ModalSubmitInteraction ou AutocompleteInteraction de acordo com o tipo delas, cada uma com data tipado.
Resiliência a mudanças¶
O Discord adiciona campos, valores de enum e tipos de componente sem aviso, e um bot não pode quebrar quando isso acontece:
- Os campos que o bostie não conhece são ignorados na decodificação.
- Os enums mapeiam um valor desconhecido para um membro estável chamado
UNKNOWN_<value>, que é igual ao valor numa comparação:MessageType(99).name == "UNKNOWN_99". - As flags mantêm os bits que o bostie não conhece, em todos os operadores, incluindo
~. - Um tipo de componente desconhecido é decodificado como
UnknownComponent, que guarda o seu JSON bruto e é enviado de volta sem mudanças quando você o passa emcomponents; um tipo de interação desconhecido é decodificado comoUnknownInteraction, que chega aos listeners deInteractionCreate, mas a nenhum comando ou rota. - Uma mensagem aninhada, como
message.referenced_messageou a mensagem de uma interação de componente, só é decodificada quando você lê a propriedade, então uma mensagem quebrada só falha onde é lida.
Um payload de evento que não cabe de jeito nenhum na sua classe, por exemplo porque falta um campo obrigatório, vai para o log uma vez e é entregue só aos listeners de RawEvent, como Eventos explica.
Payloads brutos¶
Um modelo decodificado a partir de um payload inteiro o guarda: model.raw_json guarda os bytes e model.raw os decodifica em um dict novo. Os modelos aninhados, como o autor de uma mensagem, têm raw_json = None. Todo evento também tem raw e raw_json. Eles alcançam qualquer campo que o Discord envia antes que o bostie o modele.
O repr de um modelo deixa de fora raw_json e os segredos: token, access_token, refresh_token e id_token.
Montando e copiando modelos¶
Os modelos são imutáveis e montados com argumentos nomeados. msgspec.structs.replace(model, field=value) retorna uma cópia com alguns campos alterados. Os modelos que você envia ao Discord, como Embed, AllowedMentions, PollCreateRequest, MessageReference, ForumTag, RecurrenceRule e AutoModerationAction, deixam de fora os campos que você não definiu, então um modelo decodificado é enviado de volta do jeito que chegou.
Helpers nos modelos¶
| Helper | Valor |
|---|---|
user.mention, member.mention, role.mention, channel.mention |
o texto da menção, como <@123> |
user.display_name, member.display_name |
o nome global ou o nome de usuário; para um membro, o apelido primeiro |
message.jump_url |
o link para a mensagem |
message.author_member |
o autor como um ResolvedMember, quando a mensagem traz o membro |
role.permissions, overwrite.allow, overwrite.deny |
flags Permissions |
member.permissions |
as permissões de um membro no canal da interação, quando o Discord as enviou |
webhook.url |
a URL do webhook, quando ele tem um token |
O guia de formatação cobre menções, timestamps e URLs da CDN montados a partir de IDs.
Snowflakes¶
Os IDs do Discord codificam quando o objeto foi criado. Todo modelo com um ID snowflake tem created_at, esse momento como um datetime em UTC: message.created_at, member.created_at (o da conta), emoji.created_at (None para um emoji Unicode). bostie.snowflake_time(id) faz o mesmo para um ID solto, e bostie.time_snowflake(when) retorna o menor ID criado em um momento ou, com high=True, o maior, para paginar por data. Um datetime sem fuso horário é lido como horário local.
Enums¶
bostie exporta os enums e as flags do Discord: ChannelType, MessageType, Permissions, Intents, MessageFlags, Locale, ButtonStyle e os outros da referência de enums. As flags se combinam com | e se testam com in: Permissions.SEND_MESSAGES in member_permissions. message.type.deletable diz se o Discord deixa excluir uma mensagem daquele tipo: os avisos de mensagem direta em grupo e de chamada, as mudanças de nome e de ícone do canal e a mensagem inicial de um tópico não podem ser excluídos. JSONErrorCode dá nome aos códigos de erro do Discord, como HTTPException.code os informa.