glyde/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.

A type this build has no name for fails the decode, so it reaches on_status as Undecodable rather than the handler as an Option to unwrap. A PING carries five keys, which is why most other “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)))
}

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. An id in neither resolved map is dropped, since nothing says which it is.

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

    Type 8.

A key of authorizing_integration_owners, which arrives as a JSON object key: the string “0” or “1”. Its own type rather than ApplicationIntegrationType so an unparseable key never reaches integration_types on a command create, where it would send garbage.

pub type IntegrationOwnerKey {
  KnownIntegration(
    application_command.ApplicationIntegrationType,
  )
  UnparsedIntegrationKey(String)
}

Constructors

  • UnparsedIntegrationKey(String)

    A key that was not a number at all. Keeps the raw text so two of them stay two entries; one shared sentinel would drop every one but the last.

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.Effective),
    locale: option.Option(String),
    guild_locale: option.Option(String),
    authorizing_integration_owners: dict.Dict(
      IntegrationOwnerKey,
      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. A value this build has no name for fails the decode.

pub type InteractionCallbackType {
  PongCallback
  ChannelMessageWithSourceCallback
  DeferredChannelMessageWithSourceCallback
  DeferredUpdateMessageCallback
  UpdateMessageCallback
  AutocompleteResultCallback
  ModalCallback
  PremiumRequiredCallback
  LaunchActivityCallback
}

Constructors

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

    Deprecated by Discord.

  • LaunchActivityCallback

    Activities.

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: option.Option(ComponentSubmission),
    resolved: ResolvedData,
  )
  NoData
}

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: option.Option(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. None for a component type this build does not model.

  • NoData

    Type 1 (PING) and type 5 (MODAL_SUBMIT). The envelope’s id and token are still there for a caller to answer “unsupported”.

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_: option.Option(
      application_command.ApplicationCommandOptionType,
    ),
    value: OptionValue,
    options: List(InteractionOption),
    focused: Bool,
  )
}

Constructors

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

    Arguments

    type_

    None for a value this build has no name for.

    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.

Send-only, so no unknown tail.

pub type InteractionResponse {
  Pong
  ChannelMessageWithSource(MessageCallbackData)
  DeferredChannelMessageWithSource(ephemeral: Bool)
  DeferredUpdateMessage
  UpdateMessage(message.Edit)
  AutocompleteResult(
    choices: List(
      application_command.ApplicationCommandOptionChoice(
        application_command.ChoiceValue,
      ),
    ),
  )
}

Constructors

  • Pong

    ACK a PING. A gateway bot never sends this; an HTTP-interactions bot that cannot send it can never register its endpoint.

  • ChannelMessageWithSource(MessageCallbackData)

    Reply with a new message. Create semantics.

  • DeferredChannelMessageWithSource(ephemeral: Bool)

    “Thinking…”. Turns the three-second budget into fifteen minutes; finish with PATCH /webhooks/{app}/{token}/messages/@original. EPHEMERAL is the only flag accepted, hence Bool.

  • DeferredUpdateMessage

    Components only. ACK with no loading state, and no data at all.

  • UpdateMessage(message.Edit)

    Components only. Edit semantics: Present([]) takes the components off the message. Field for field and key for key this is a message edit, so it carries the same type, built with update_data.

  • Autocomplete only. An empty list is legal and means no suggestions. Max 25. ChoiceValue because the option being typed is only known at runtime, so this response cannot be tied to one value type.

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

A value this build has no name for fails the decode.

pub type InteractionType {
  PingInteraction
  ApplicationCommandInteraction
  MessageComponentInteraction
  AutocompleteInteraction
  ModalSubmitInteraction
}

Constructors

  • PingInteraction

    HTTP interactions only. Never arrives over the gateway.

  • ApplicationCommandInteraction
  • MessageComponentInteraction
  • AutocompleteInteraction
  • ModalSubmitInteraction

    Its data is not modelled, so data decodes as NoData.

One pick from a mentionable select, which is the only component that can return a user and a role in the same list. An id in neither resolved map is dropped from the list.

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

Constructors

Callback data for ChannelMessageWithSource. Same shape and rules as Draft.

pub type MessageCallbackData {
  MessageCallbackData(
    content: option.Option(String),
    tts: Bool,
    embeds: List(embed.Embed),
    components: List(component.Component),
    files: List(attachment.File),
    allowed_mentions: option.Option(mentions.AllowedMentions),
    flags: flags.Flags(message.MessageFlag),
  )
}

Constructors

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
}

Constructors

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

    The value key was absent, or was 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.Effective),
    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

Who to answer. One value, because the ids and the token have to come from the same interaction: the Id tags catch an id swapped for an id, and this catches the token of the other interaction in flight.

pub opaque type Responder

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(
  api: api.Api,
  responder: Responder,
  response: InteractionResponse,
) -> Result(Nil, api.CallFailure)

POST /interactions/{interaction.id}/{token}/callback, the first answer. Answers 204: use callback_with_response if you need the message id.

pub fn callback_call(
  responder: Responder,
  response: InteractionResponse,
) -> rest.Call(Nil)

The Call for [callback], for building the request without sending it.

pub fn callback_type(
  response: InteractionResponse,
) -> InteractionCallbackType

The callback type Discord reads off the envelope. Shares its numbering with callback_type_from_int, so encode and decode use one table.

pub fn callback_type_from_int(
  value: Int,
) -> option.Option(InteractionCallbackType)
pub fn callback_type_to_int(
  value: InteractionCallbackType,
) -> Int
pub fn callback_type_to_json(
  value: InteractionCallbackType,
) -> json.Json
pub fn callback_with_response(
  api: api.Api,
  responder: Responder,
  response: InteractionResponse,
) -> Result(InteractionCallbackResponse, api.CallFailure)

POST /interactions/{interaction.id}/{token}/callback?with_response=true, answering 200 with the callback resource instead of 204.

pub fn callback_with_response_call(
  responder: Responder,
  response: InteractionResponse,
) -> rest.Call(InteractionCallbackResponse)

The Call for [callback_with_response], for building the request without sending it.

pub fn channel_option(
  options: List(InteractionOption),
  name: String,
) -> option.Option(id.Id(id.Channel))
pub fn create_followup(
  api: api.Api,
  responder: Responder,
  body: body.Body,
) -> Result(message.Message, api.CallFailure)

POST /webhooks/{application.id}/{token}?wait=true, an extra message on the same interaction. Capped at five when the app is user-installed and not a member of the server, 40094 past that.

pub fn create_followup_call(
  responder: Responder,
  body: body.Body,
) -> rest.Call(message.Message)

The Call for [create_followup], for building the request without sending it.

pub fn data_decoder(
  interaction_type: InteractionType,
) -> decode.Decoder(InteractionData)

The envelope’s type picks the decoder. A type whose data this build does not model gives NoData, so the envelope’s id and token are still there.

pub fn defer(
  api: api.Api,
  interaction: Interaction,
) -> Result(Nil, api.CallFailure)

Show “thinking…” and buy fifteen minutes. Callback type 5. Send within three seconds, then finish with edit_response. The reply is public: the eventual edit cannot make it ephemeral, so pick defer_ephemeral now if only the invoker should see it.

pub fn defer_call(interaction: Interaction) -> rest.Call(Nil)

The Call for [defer], for building the request without sending it.

pub fn defer_ephemeral(
  api: api.Api,
  interaction: Interaction,
) -> Result(Nil, api.CallFailure)

defer with the reply visible only to the invoker. Fixed at defer time: the eventual edit_response cannot change it back to public.

pub fn defer_ephemeral_call(
  interaction: Interaction,
) -> rest.Call(Nil)

The Call for [defer_ephemeral], for building the request without sending it.

pub fn delete_followup(
  api: api.Api,
  responder: Responder,
  message: id.Id(id.Message),
) -> Result(Nil, api.CallFailure)

DELETE /webhooks/{application.id}/{token}/messages/{message.id}.

pub fn delete_followup_call(
  responder: Responder,
  message: id.Id(id.Message),
) -> rest.Call(Nil)

The Call for [delete_followup], for building the request without sending it.

pub fn delete_original_response(
  api: api.Api,
  responder: Responder,
) -> Result(Nil, api.CallFailure)

DELETE /webhooks/{application.id}/{token}/messages/@original, which works on an ephemeral response as well.

pub fn delete_original_response_call(
  responder: Responder,
) -> rest.Call(Nil)

The Call for [delete_original_response], for building the request without sending it.

pub fn edit_followup(
  api: api.Api,
  responder: Responder,
  message: id.Id(id.Message),
  body: body.Body,
) -> Result(message.Message, api.CallFailure)

PATCH /webhooks/{application.id}/{token}/messages/{message.id}.

pub fn edit_followup_call(
  responder: Responder,
  message: id.Id(id.Message),
  body: body.Body,
) -> rest.Call(message.Message)

The Call for [edit_followup], for building the request without sending it.

pub fn edit_original_response(
  api: api.Api,
  responder: Responder,
  body: body.Body,
) -> Result(message.Message, api.CallFailure)

PATCH /webhooks/{application.id}/{token}/messages/@original. Turns a deferred response into a real one, and edits one already sent.

pub fn edit_original_response_call(
  responder: Responder,
  body: body.Body,
) -> rest.Call(message.Message)

The Call for [edit_original_response], for building the request without sending it.

pub fn edit_response(
  api: api.Api,
  interaction: Interaction,
  edit: message.Edit,
) -> Result(message.Message, api.CallFailure)

Turn a deferred response into a real one, or edit one already sent. PATCH /webhooks/{application.id}/{token}/messages/@original.

pub fn edit_response_call(
  interaction: Interaction,
  edit: message.Edit,
) -> rest.Call(message.Message)

The Call for [edit_response], for building the request without sending it.

pub fn empty_resolved() -> ResolvedData
pub fn ephemeral(
  data: MessageCallbackData,
) -> MessageCallbackData

Only the invoking user sees it.

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 fn followup(
  api: api.Api,
  interaction: Interaction,
  draft: message.Draft,
) -> Result(message.Message, api.CallFailure)

A second message on the same interaction, after respond or defer has answered it. POST /webhooks/{application.id}/{token}?wait=true.

pub fn followup_call(
  interaction: Interaction,
  draft: message.Draft,
) -> rest.Call(message.Message)

The Call for [followup], for building the request without sending it.

pub fn from_draft(draft: message.Draft) -> MessageCallbackData

A Draft as callback data, so message.text and its setters build an interaction reply as well as a channel post. sticker_ids, reference and nonce are dropped: an interaction response is not a channel create, and Discord takes none of them here.

pub fn get_followup(
  api: api.Api,
  responder: Responder,
  message: id.Id(id.Message),
) -> Result(message.Message, api.CallFailure)

GET /webhooks/{application.id}/{token}/messages/{message.id}.

pub fn get_followup_call(
  responder: Responder,
  message: id.Id(id.Message),
) -> rest.Call(message.Message)

The Call for [get_followup], for building the request without sending it.

pub fn get_original_response(
  api: api.Api,
  responder: Responder,
) -> Result(message.Message, api.CallFailure)

GET /webhooks/{application.id}/{token}/messages/@original.

pub fn get_original_response_call(
  responder: Responder,
) -> rest.Call(message.Message)

The Call for [get_original_response], for building the request without sending it.

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,
) -> option.Option(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 message_data_to_json(
  data: MessageCallbackData,
) -> json.Json
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 respond(
  api: api.Api,
  interaction: Interaction,
  draft: message.Draft,
) -> Result(Nil, api.CallFailure)

Reply with a message. Callback type 4. The first response owed within three seconds; past that Discord drops the interaction and this call answers 404. defer first if the reply takes longer to build.

pub fn respond_call(
  interaction: Interaction,
  draft: message.Draft,
) -> rest.Call(Nil)

The bare Call, for driving glyde/rest yourself.

pub fn responder(
  interaction interaction: id.Id(id.Interaction),
  application application: id.Id(id.Application),
  token token: InteractionToken,
) -> Responder

The HTTP-interactions path, and a token kept somewhere across a restart. Prefer responding_to where there is an Interaction to hand.

pub fn responding_to(interaction: Interaction) -> Responder

The gateway path. Everything comes off the one INTERACTION_CREATE, so the three cannot disagree.

pub fn response_body(response: InteractionResponse) -> body.Body

A ready-to-send body for the callback route, files already paired to their attachments entries. callback calls this for you; an HTTP-interactions bot writing straight to its own HTTP response uses it directly.

The callback nests its attachments array under data, so the document is finished here rather than left open: a top-level array as well would name the same parts twice, and the copy without the kept ones deletes them.

pub fn response_files(
  response: InteractionResponse,
) -> List(attachment.File)

The files the multipart body has to carry, in files[n] order.

pub fn response_to_json(
  response: InteractionResponse,
) -> json.Json
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 text(content: String) -> MessageCallbackData
pub fn update_data(
  mentions: mentions.AllowedMentions,
) -> message.Edit

Same as message.new_edit: the mention policy is not optional.

pub fn update_data_to_json(data: message.Edit) -> json.Json
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