glyde/model/interaction

Interactions: what arrives when someone runs a command, presses a button or types into an autocomplete box.

data is decoded on the envelope’s type, never on the shape of data: command (2) and autocomplete (4) send identical objects, so shape sniffing answers an autocomplete keystroke with a message per letter.

The decode is all or nothing, so every enum has an unknown tail. A PING carries five keys, which is why most “required” fields are optional here.

Types

One entry of authorizing_integration_owners. A guild install invoked in the app’s own DM has no guild to name, and Discord says so with a sentinel rather than by leaving the entry out.

pub type AuthorizingOwner {
  OwnedBy(String)
  NoOwner
}

Constructors

  • OwnedBy(String)

    A snowflake: a guild id under GuildInstall, a user id under UserInstall.

  • NoOwner

What the component sent back, one variant per component_type. Discord writes every submission as the same values array of strings, so this is what says whether reading it means anything and what the strings are.

pub type ComponentSubmission {
  ButtonPress
  StringSelect(values: List(String))
  UserSelect(users: List(id.Id(id.User)))
  RoleSelect(roles: List(id.Id(id.Role)))
  MentionableSelect(mentions: List(Mentionable))
  ChannelSelect(channels: List(id.Id(id.Channel)))
  UnknownSubmission(component_type: Int, values: List(String))
}

Constructors

  • ButtonPress

    Type 2. A button submits nothing but its custom_id.

  • StringSelect(values: List(String))

    Type 3. The value of each chosen option, not its label.

  • UserSelect(users: List(id.Id(id.User)))

    Type 5.

  • RoleSelect(roles: List(id.Id(id.Role)))

    Type 6.

  • MentionableSelect(mentions: List(Mentionable))

    Type 7. Users and roles come back in one list.

  • ChannelSelect(channels: List(id.Id(id.Channel)))

    Type 8.

  • UnknownSubmission(component_type: Int, values: List(String))

    A component type this build does not know, with its values as sent.

pub type Interaction {
  Interaction(
    id: id.Id(id.Interaction),
    application_id: id.Id(id.Application),
    type_: InteractionType,
    data: InteractionData,
    guild: option.Option(InteractionGuild),
    guild_id: option.Option(id.Id(id.Guild)),
    channel: option.Option(channel.Channel),
    channel_id: option.Option(id.Id(id.Channel)),
    member: option.Option(member.GuildMember),
    user: option.Option(user.User),
    token: InteractionToken,
    version: Int,
    message: option.Option(message.Message),
    app_permissions: option.Option(permissions.Permissions),
    locale: option.Option(String),
    guild_locale: option.Option(String),
    authorizing_integration_owners: dict.Dict(
      application_command.ApplicationIntegrationType,
      AuthorizingOwner,
    ),
    context: option.Option(
      application_command.InteractionContextType,
    ),
    attachment_size_limit: option.Option(Int),
  )
}

Constructors

pub type InteractionCallback {
  InteractionCallback(
    id: id.Id(id.Interaction),
    type_: InteractionType,
    response_message_id: option.Option(id.Id(id.Message)),
    response_message_loading: option.Option(Bool),
    response_message_ephemeral: option.Option(Bool),
  )
}

Constructors

pub type InteractionCallbackResource {
  InteractionCallbackResource(
    type_: InteractionCallbackType,
    message: option.Option(message.Message),
  )
}

Constructors

The body of POST …/callback?with_response=true. Without that parameter the route answers 204.

pub type InteractionCallbackResponse {
  InteractionCallbackResponse(
    interaction: InteractionCallback,
    resource: option.Option(InteractionCallbackResource),
  )
}

Constructors

The numbering skips 11, so never index this by position.

pub type InteractionCallbackType {
  PongCallback
  ChannelMessageWithSourceCallback
  DeferredChannelMessageWithSourceCallback
  DeferredUpdateMessageCallback
  UpdateMessageCallback
  AutocompleteResultCallback
  ModalCallback
  PremiumRequiredCallback
  LaunchActivityCallback
  UnknownCallbackType(Int)
}

Constructors

  • PongCallback
  • ChannelMessageWithSourceCallback
  • DeferredChannelMessageWithSourceCallback
  • DeferredUpdateMessageCallback
  • UpdateMessageCallback
  • AutocompleteResultCallback
  • ModalCallback
  • PremiumRequiredCallback

    Deprecated by Discord.

  • LaunchActivityCallback

    Activities.

  • UnknownCallbackType(Int)
pub type InteractionData {
  CommandData(
    id: id.Id(id.Command),
    name: String,
    type_: application_command.ApplicationCommandType,
    resolved: ResolvedData,
    options: List(InteractionOption),
    guild_id: option.Option(id.Id(id.Guild)),
    target_id: option.Option(String),
  )
  AutocompleteData(
    id: id.Id(id.Command),
    name: String,
    type_: application_command.ApplicationCommandType,
    options: List(InteractionOption),
    guild_id: option.Option(id.Id(id.Guild)),
  )
  ComponentData(
    custom_id: String,
    submission: ComponentSubmission,
    resolved: ResolvedData,
  )
  NoData
  UnknownData(type_: Int, raw: dynamic.Dynamic)
}

Constructors

  • CommandData(
      id: id.Id(id.Command),
      name: String,
      type_: application_command.ApplicationCommandType,
      resolved: ResolvedData,
      options: List(InteractionOption),
      guild_id: option.Option(id.Id(id.Guild)),
      target_id: option.Option(String),
    )

    Type 2. A slash command or a context-menu command.

    Arguments

    name

    The top-level command name only. Subcommand names live in options.

    resolved

    Absent resolved decodes to an empty one, so no Option to unwrap.

    guild_id

    Where the command is REGISTERED, not where it was invoked.

    target_id

    A user id for USER commands, a message id for MESSAGE ones. Read it through target_user_id and target_message_id.

  • AutocompleteData(
      id: id.Id(id.Command),
      name: String,
      type_: application_command.ApplicationCommandType,
      options: List(InteractionOption),
      guild_id: option.Option(id.Id(id.Guild)),
    )

    Type 4. Byte-identical to CommandData on the wire and a different thing to answer. options is partial, and the one being typed has focused.

  • ComponentData(
      custom_id: String,
      submission: ComponentSubmission,
      resolved: ResolvedData,
    )

    Type 3. A button press or a select submission.

    Arguments

    submission

    What was submitted, tagged by the component that sent it. A button press carries nothing, so there is no empty values to read.

  • NoData

    Type 1 (PING), and any future type that carries no data key.

  • UnknownData(type_: Int, raw: dynamic.Dynamic)

    Type 5 (MODAL_SUBMIT) today. Carries the envelope type and the undecoded data, so a caller can hand-roll against it.

Not a Guild: the guild decoder fails on these three fields.

pub type InteractionGuild {
  InteractionGuild(
    id: id.Id(id.Guild),
    locale: String,
    features: List(String),
  )
}

Constructors

  • InteractionGuild(
      id: id.Id(id.Guild),
      locale: String,
      features: List(String),
    )

One parameter of an invoked command. Nests at most three deep: group, subcommand, then the value-bearing options.

pub type InteractionOption {
  InteractionOption(
    name: String,
    type_: application_command.ApplicationCommandOptionType,
    value: OptionValue,
    options: List(InteractionOption),
    focused: Bool,
  )
}

Constructors

  • InteractionOption(
      name: String,
      type_: application_command.ApplicationCommandOptionType,
      value: OptionValue,
      options: List(InteractionOption),
      focused: Bool,
    )

    Arguments

    value

    NoValue for subcommands and groups, which carry options instead, and for an autocomplete option not yet typed.

    options

    Empty unless this is a subcommand or a subcommand group.

    focused

    True on the option the user is typing. Autocomplete only.

The credential half of an interaction: this plus the id answers as the bot for fifteen minutes with no Authorization header. Opaque and with no to_string, so echo interaction cannot spill it. A closure, not a field, because string.inspect ignores opaqueness.

pub opaque type InteractionToken
pub type InteractionType {
  PingInteraction
  ApplicationCommandInteraction
  MessageComponentInteraction
  AutocompleteInteraction
  ModalSubmitInteraction
  UnknownInteractionType(Int)
}

Constructors

  • PingInteraction

    HTTP interactions only. Never arrives over the gateway.

  • ApplicationCommandInteraction
  • MessageComponentInteraction
  • AutocompleteInteraction
  • ModalSubmitInteraction

    Not modelled, so its data lands in UnknownData(5, raw).

  • UnknownInteractionType(Int)

One pick from a mentionable select, which is the only component that can return a user and a role in the same list.

pub type Mentionable {
  MentionedUser(id: id.Id(id.User))
  MentionedRole(id: id.Id(id.Role))
  UnknownMentionable(raw: String)
}

Constructors

  • MentionedUser(id: id.Id(id.User))
  • MentionedRole(id: id.Id(id.Role))
  • UnknownMentionable(raw: String)

    In neither resolved map, so nothing here says which it is. Tagging it either way puts a role id on a route that takes a user.

The snowflake option types arrive as a StringValue holding the id. Look it up in resolved, or use the typed accessors below.

pub type OptionValue {
  StringValue(String)
  IntValue(Int)
  FloatValue(Float)
  BoolValue(Bool)
  NoValue
  UnknownValue(dynamic.Dynamic)
}

Constructors

  • StringValue(String)
  • IntValue(Int)
  • FloatValue(Float)
  • BoolValue(Bool)
  • NoValue

    The value key was absent.

  • UnknownValue(dynamic.Dynamic)

    Present, and not a string, number or bool.

Narrower than Channel except for permissions, the invoking user’s computed permissions, which a Channel decode would drop.

pub type ResolvedChannel {
  ResolvedChannel(
    id: id.Id(id.Channel),
    type_: channel.ChannelType,
    name: option.Option(String),
    permissions: option.Option(permissions.Permissions),
    parent_id: option.Option(id.Id(id.Channel)),
    thread_metadata: option.Option(channel.ThreadMetadata),
  )
}

Constructors

Everything the user picked, already fetched. Absent maps decode to empty.

pub type ResolvedData {
  ResolvedData(
    users: dict.Dict(id.Id(id.User), user.User),
    members: dict.Dict(id.Id(id.User), member.GuildMember),
    roles: dict.Dict(id.Id(id.Role), role.Role),
    channels: dict.Dict(id.Id(id.Channel), ResolvedChannel),
    messages: dict.Dict(id.Id(id.Message), message.Message),
    attachments: dict.Dict(
      id.Id(id.Attachment),
      attachment.Attachment,
    ),
  )
}

Constructors

Values

pub fn attachment_option(
  options: List(InteractionOption),
  name: String,
) -> option.Option(id.Id(id.Attachment))
pub fn authorizing_guild_id(
  interaction: Interaction,
) -> option.Option(id.Id(id.Guild))

The guild that installed the app, when the interaction ran under a guild install. None in a bot DM, where Discord sends the sentinel instead.

pub fn authorizing_user_id(
  interaction: Interaction,
) -> option.Option(id.Id(id.User))

The user who installed the app, when the interaction ran under a user install.

pub fn bool_option(
  options: List(InteractionOption),
  name: String,
) -> option.Option(Bool)
pub fn callback_type_from_int(
  value: Int,
) -> InteractionCallbackType
pub fn callback_type_to_int(
  value: InteractionCallbackType,
) -> Int
pub fn callback_type_to_json(
  value: InteractionCallbackType,
) -> json.Json
pub fn channel_option(
  options: List(InteractionOption),
  name: String,
) -> option.Option(id.Id(id.Channel))
pub fn data_decoder(
  interaction_type: InteractionType,
) -> decode.Decoder(InteractionData)

The envelope’s type picks the decoder. A type this build does not model keeps its number and its undecoded payload.

pub fn empty_resolved() -> ResolvedData
pub fn find_option(
  options: List(InteractionOption),
  name: String,
) -> option.Option(InteractionOption)

A named parameter of the command the user actually invoked. Descends through subcommands first, so /config set key:red finds key from the top-level options, the same depth focused_option reads at. A group or a subcommand is not a parameter and is not found here: subcommand_path returns those names.

pub fn float_option(
  options: List(InteractionOption),
  name: String,
) -> option.Option(Float)
pub fn focused_option(
  options: List(InteractionOption),
) -> option.Option(InteractionOption)

The option being typed, during autocomplete. Descends into subcommands: /config set key:<typing> is two levels down.

pub const initial_response_ms: Int

Discord’s deadline for the first response to an interaction.

pub fn int_option(
  options: List(InteractionOption),
  name: String,
) -> option.Option(Int)
pub fn interaction_guild_decoder() -> decode.Decoder(
  InteractionGuild,
)
pub fn interaction_token(raw: String) -> InteractionToken

For a token that did not come through this decoder: an HTTP-interactions endpoint reading it out of its own request, or one kept across a restart.

pub fn interaction_type_decoder() -> decode.Decoder(
  InteractionType,
)
pub fn interaction_type_from_int(value: Int) -> InteractionType
pub fn interaction_type_to_int(value: InteractionType) -> Int
pub fn interaction_type_to_json(
  value: InteractionType,
) -> json.Json
pub fn invoking_user(
  interaction: Interaction,
) -> option.Option(user.User)

In a guild the user is at member.user, in a DM at user.

pub fn mentionable_option(
  options: List(InteractionOption),
  name: String,
  resolved: ResolvedData,
) -> option.Option(Mentionable)

The one snowflake accessor that reads a MENTIONABLE, because the resolved that says user or role is handed to it. Take it off the same CommandData the options came from.

pub fn remaining_response_budget_ms(
  interaction: Interaction,
  now_ms now_ms: Int,
) -> Int

Milliseconds left for the first response, counted from the interaction’s snowflake and not from receipt. 0 once closed, and 0 for a non-snowflake.

pub fn resolved_channel_decoder() -> decode.Decoder(
  ResolvedChannel,
)
pub fn reveal_token(token: InteractionToken) -> String

The token as a path segment wants it. glyde/api/interaction is the only caller that needs this; anywhere else, it is a secret being taken out.

pub fn role_option(
  options: List(InteractionOption),
  name: String,
) -> option.Option(id.Id(id.Role))

MENTIONABLE is not accepted, for the reason user_option gives.

pub fn string_option(
  options: List(InteractionOption),
  name: String,
) -> option.Option(String)
pub fn subcommand_path(
  options: List(InteractionOption),
) -> #(List(String), List(InteractionOption))

Flatten /group sub key:value to #(["group", "sub"], [the key option]).

pub fn user_option(
  options: List(InteractionOption),
  name: String,
) -> option.Option(id.Id(id.User))

MENTIONABLE is not accepted here: only resolved says whether its id is a user or a role, so reading one as a user is a coin flip. Read one with mentionable_option, which takes the resolved that settles it.

Search Document