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 underUserInstall. -
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
-
ButtonPressType 2. A button submits nothing but its
custom_id. -
StringSelect(values: List(String))Type 3. The
valueof each chosen option, not its label. -
Type 5.
-
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
valuesas 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
-
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), )Arguments
- type_
-
Selects which
datavariant you get. Match ondata, not on this. - data
-
NoDatafor a PING, not anOption, so a handler writes onecase. - guild_id
-
Absent, not null, in DMs.
- channel
-
A PARTIAL channel: assume only
idandtype_are populated. - channel_id
-
Absent on a PING.
- member
-
Present only in a guild, where
member.useris the invoking user. - user
-
Present only in a DM. Use
invoking_userto get either. - token
-
Valid 15 minutes, first response due in 3 seconds. Hand it to
glyde/api/interaction.responding_torather than reading it out. - version
-
Always 1.
- message
-
The message a component was attached to.
- app_permissions
-
What the APP may do here, not what the user may do. Absent on a PING.
- authorizing_integration_owners
-
Who installed the app, one entry per install type it ran under. Empty on a PING.
authorizing_guild_idandauthorizing_user_idread it as typed ids. - attachment_size_limit
-
Bytes. Absent on a PING.
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
-
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), )Arguments
- response_message_id
-
The message your response created. Absent for a deferred response and for autocomplete.
pub type InteractionCallbackResource {
InteractionCallbackResource(
type_: InteractionCallbackType,
message: option.Option(message.Message),
)
}
Constructors
-
InteractionCallbackResource( type_: InteractionCallbackType, message: option.Option(message.Message), )Arguments
- message
-
Only for CHANNEL_MESSAGE_WITH_SOURCE and UPDATE_MESSAGE.
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
-
InteractionCallbackResponse( interaction: InteractionCallback, resource: option.Option(InteractionCallbackResource), )
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 -
PremiumRequiredCallbackDeprecated by Discord.
-
LaunchActivityCallbackActivities.
-
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
resolveddecodes to an empty one, so noOptionto 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_idandtarget_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
CommandDataon the wire and a different thing to answer.optionsis partial, and the one being typed hasfocused. -
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
valuesto read.
-
NoDataType 1 (PING), and any future type that carries no
datakey. -
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.
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
-
NoValuefor subcommands and groups, which carryoptionsinstead, 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
-
PingInteractionHTTP interactions only. Never arrives over the gateway.
-
ApplicationCommandInteraction -
MessageComponentInteraction -
AutocompleteInteraction -
ModalSubmitInteractionNot 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
-
-
-
UnknownMentionable(raw: String)In neither
resolvedmap, 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) -
NoValueThe
valuekey 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
-
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), )Arguments
- thread_metadata
-
Present when the resolved channel is a thread.
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
-
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, ), )Arguments
- members
-
Partial: no
user,deaformute. The id is already the map key. - messages
-
Only ever populated for MESSAGE context-menu commands.
- attachments
-
Never populated for message components.
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_decoder() -> decode.Decoder(InteractionCallback)
pub fn callback_resource_decoder() -> decode.Decoder(
InteractionCallbackResource,
)
pub fn callback_response_decoder() -> decode.Decoder(
InteractionCallbackResponse,
)
pub fn callback_type_decoder() -> decode.Decoder(
InteractionCallbackType,
)
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 decoder() -> decode.Decoder(Interaction)
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 option_decoder() -> decode.Decoder(InteractionOption)
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 resolved_decoder() -> decode.Decoder(ResolvedData)
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 target_message_id(
data: InteractionData,
) -> option.Option(id.Id(id.Message))
pub fn target_user_id(
data: InteractionData,
) -> option.Option(id.Id(id.User))
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.