tadpole/model/message

The Discord message object for MESSAGE_CREATE, plus the partial object MESSAGE_UPDATE sends. Attachments are kept to a skeleton: bots that need them fetch the message again anyway. message_type is Discord’s raw integer; naming a sum type now would freeze it against Discord’s growing list.

Two records, two payloads:

Why fields are Option

An Option means Discord omits the key or nulls it in the payloads this record models: guild_id in DMs, edited_timestamp before the first edit, webhook_id for non-webhook authors. Defaults differ by field: content arrives as "" for embed-only posts, tts / mention_everyone / pinned default to False, message_type to 0 — those are documented Discord semantics, not guesses. Unknown fields are ignored.

Why some fields are String, not IDs

Attachment.id is a plain String: attachments are rare in this milestone and snowflake validation would only add a failure mode without buying type safety anyone uses yet. Every field the library itself dereferences — id, channel_id, guild_id, webhook_id, mention role ids — is a typed ID that fails the decode if it is not a snowflake.

Types

pub type Attachment {
  Attachment(
    id: String,
    filename: String,
    size: Int,
    url: String,
  )
}

Constructors

  • Attachment(id: String, filename: String, size: Int, url: String)

    Arguments

    id

    Deliberately String, not a typed ID: attachments are rare in this milestone and the snowflake validation buys nothing here yet.

pub type Message {
  Message(
    id: ids.MessageId,
    channel_id: ids.ChannelId,
    guild_id: option.Option(ids.GuildId),
    author: user.User,
    content: String,
    timestamp: String,
    edited_timestamp: option.Option(String),
    tts: Bool,
    mention_everyone: Bool,
    mentions: List(user.User),
    mention_role_ids: List(ids.RoleId),
    attachments: List(Attachment),
    pinned: Bool,
    webhook_id: option.Option(ids.WebhookId),
    message_type: Int,
  )
}

Constructors

  • Message(
      id: ids.MessageId,
      channel_id: ids.ChannelId,
      guild_id: option.Option(ids.GuildId),
      author: user.User,
      content: String,
      timestamp: String,
      edited_timestamp: option.Option(String),
      tts: Bool,
      mention_everyone: Bool,
      mentions: List(user.User),
      mention_role_ids: List(ids.RoleId),
      attachments: List(Attachment),
      pinned: Bool,
      webhook_id: option.Option(ids.WebhookId),
      message_type: Int,
    )

    Arguments

    guild_id

    None in DMs.

    content

    Empty when the message is an embed-only or attachment-only post.

    timestamp

    ISO8601 timestamp, passed through exactly as Discord sent it (always UTC, e.g. 2026-09-07T12:00:00.000000+00:00).

    edited_timestamp

    Stays None until the first edit; then it is Discord’s ISO8601 edit time.

    webhook_id

    Set when a webhook sent the message.

    message_type

    Discord’s raw MESSAGE type integer: 0 DEFAULT, 1 RECIPIENT_ADD, 19 REPLY, 20 APPLICATION_COMMAND, and so on. Kept raw because Discord adds types faster than libraries track them.

Decoder for the partial message object MESSAGE_UPDATE carries: the IDs are always present, every other field may be absent. Absent fields read as None rather than falling back to a cached message — merging old and new state is the caller’s job, not the decoder’s.

pub type MessageUpdate {
  MessageUpdate(
    id: ids.MessageId,
    channel_id: ids.ChannelId,
    content: option.Option(String),
    author: option.Option(user.User),
    edited_timestamp: option.Option(String),
    pinned: option.Option(Bool),
  )
}

Constructors

Values

pub fn attachment_decoder() -> decode.Decoder(Attachment)

Attachment decoder, exposed for later modules that meet attachments outside a message payload.

pub fn decoder() -> decode.Decoder(Message)

Decoder for the full message object MESSAGE_CREATE carries. Discord sends IDs as strings; a non-snowflake string in id, channel_id, guild_id, webhook_id, or a mention role fails the decode. Fields Discord reserves for rarely used features are optional here and default sensibly: tts False, mention_everyone False, pinned False. Unknown fields are ignored.

pub fn from_json(
  payload: String,
) -> Result(Message, error.TadpoleError)

Decode a MESSAGE_CREATE payload: the message object, not a gateway frame. Fails with error.DecodeFailed when the payload is not valid JSON, a required field is missing or mistyped, or an ID is not a snowflake. The error’s event field stays None; wiring that knows the event name fills it in.

pub fn update_decoder() -> decode.Decoder(MessageUpdate)

Decoder for MESSAGE_UPDATE’s d. Fails the same way as the full message decoder when the payload is not JSON or an ID is not a snowflake.

pub fn update_from_json(
  payload: String,
) -> Result(MessageUpdate, error.TadpoleError)

Decode a MESSAGE_UPDATE payload: the partial message object, not a gateway frame. Fails with error.DecodeFailed when the payload is not valid JSON or one of the always-present IDs is not a snowflake.

Search Document