glyde/event
Gateway dispatch events, decoded.
decode is total: an unmodelled name, or a payload that did not fit, comes
back as Raw with d untouched, so a host’s case needs no error arm.
dispatch is the same decode with the outcome kept apart, which is how
glyde’s own schema drift is told from an event it never modelled.
Types
pub type Dispatch {
Dispatch(name: String, data: dynamic.Dynamic, outcome: Outcome)
}
Constructors
-
Dispatch(name: String, data: dynamic.Dynamic, outcome: Outcome)Arguments
- data
-
dexactly as it arrived, the hatch for anything glyde does not model.
One decoded dispatch.
pub type Event {
ReadyEvent(ready.Ready)
ResumedEvent
RateLimitedEvent(RateLimited)
GuildCreateAvailable(guild: guild.Guild)
GuildCreateUnavailable(id: id.Id(id.Guild))
GuildUpdate(guild: guild.Guild)
GuildUnavailable(id: id.Id(id.Guild))
GuildRemoved(id: id.Id(id.Guild))
GuildMemberAdd(
guild_id: id.Id(id.Guild),
member: member.GuildMember,
)
GuildMemberRemove(guild_id: id.Id(id.Guild), user: user.User)
GuildMemberUpdate(
guild_id: id.Id(id.Guild),
member: member.GuildMember,
)
GuildMembersChunk(MembersChunk)
GuildRoleCreate(guild_id: id.Id(id.Guild), role: role.Role)
GuildRoleUpdate(guild_id: id.Id(id.Guild), role: role.Role)
GuildRoleDelete(
guild_id: id.Id(id.Guild),
role_id: id.Id(id.Role),
)
GuildBanAdd(guild_id: id.Id(id.Guild), user: user.User)
GuildBanRemove(guild_id: id.Id(id.Guild), user: user.User)
GuildEmojisUpdate(
guild_id: id.Id(id.Guild),
emojis: List(emoji.GuildEmoji),
)
ChannelCreate(channel: channel.Channel)
ChannelUpdate(channel: channel.Channel)
ChannelDelete(channel: channel.Channel)
ChannelPinsUpdate(
guild_id: option.Option(id.Id(id.Guild)),
channel_id: id.Id(id.Channel),
last_pin_timestamp: option.Option(String),
)
ThreadCreate(channel: channel.Channel)
ThreadUpdate(channel: channel.Channel)
ThreadDelete(
id: id.Id(id.Channel),
guild_id: id.Id(id.Guild),
parent_id: option.Option(id.Id(id.Channel)),
type_: channel.ChannelType,
)
MessageCreate(message: message.Message)
MessageUpdate(message: message.MessageUpdate)
MessageDelete(
id: id.Id(id.Message),
channel_id: id.Id(id.Channel),
guild_id: option.Option(id.Id(id.Guild)),
)
MessageDeleteBulk(
ids: List(id.Id(id.Message)),
channel_id: id.Id(id.Channel),
guild_id: option.Option(id.Id(id.Guild)),
)
MessageReactionAdd(ReactionAdd)
MessageReactionRemove(ReactionRemove)
MessageReactionRemoveAll(
channel_id: id.Id(id.Channel),
message_id: id.Id(id.Message),
guild_id: option.Option(id.Id(id.Guild)),
)
MessageReactionRemoveEmoji(
channel_id: id.Id(id.Channel),
message_id: id.Id(id.Message),
guild_id: option.Option(id.Id(id.Guild)),
emoji: emoji.Emoji,
)
InteractionCreate(interaction: interaction.Interaction)
TypingStartEvent(TypingStart)
UserUpdate(user: user.User)
VoiceStateUpdate(voice_state.VoiceState)
VoiceServerUpdate(
token: String,
guild_id: id.Id(id.Guild),
endpoint: option.Option(String),
)
Raw(name: String, data: dynamic.Dynamic)
}
Constructors
-
ReadyEvent(ready.Ready)READY. The handshake reads
session_idandresume_gateway_urlout of this samed, so a decoder bug here cannot cost a session. -
ResumedEventRESUMED. Replay is finished and everything after this is new.
dis{"_trace":[…]}, Discord’s own routing breadcrumb. -
RateLimitedEvent(RateLimited)RATE_LIMITED. A dispatch, not an opcode: it arrives with
"op":0and advances the sequence, so a client switching onopnever sees it. -
GuildCreateAvailable(guild: guild.Guild)GUILD_CREATE for a guild that is up. Sent on connect for every guild the bot is in, then again whenever it joins one.
-
GUILD_CREATE for a guild that is still down. Two keys, no more.
-
GuildUpdate(guild: guild.Guild) -
GUILD_DELETE during a Discord outage. Still the bot’s guild, and it comes back as a GUILD_CREATE. Do not evict it from a cache.
-
GUILD_DELETE because the bot was kicked or banned, or the guild was deleted. This one is permanent.
-
GuildMemberAdd( guild_id: id.Id(id.Guild), member: member.GuildMember, )dis the member object with an extraguild_id, which is why the id sits beside the member rather than inside it. -
-
GuildMemberUpdate( guild_id: id.Id(id.Guild), member: member.GuildMember, )A partial member:
deaf,muteandflagscan be missing despite the docs. Privileged behindGuildMembers, bar the bot’s own member. -
GuildMembersChunk(MembersChunk)The answer to REQUEST_GUILD_MEMBERS, at most 1000 members per chunk.
-
-
-
-
Needs
GuildModerationplus either BAN_MEMBERS or VIEW_AUDIT_LOG. -
-
GuildEmojisUpdate( guild_id: id.Id(id.Guild), emojis: List(emoji.GuildEmoji), )emojisis the guild’s whole list, never a delta. Replace, do not merge: a deleted emoji is only ever signalled by its absence here. -
ChannelCreate(channel: channel.Channel) -
ChannelUpdate(channel: channel.Channel) -
ChannelDelete(channel: channel.Channel) -
ChannelPinsUpdate( guild_id: option.Option(id.Id(id.Guild)), channel_id: id.Id(id.Channel), last_pin_timestamp: option.Option(String), )last_pin_timestampis ISO-8601, andNoneonce the last pin is removed. The event does not say which message was pinned. -
ThreadCreate(channel: channel.Channel)channelcarriesnewly_created, which tells a thread opened on a brand new message from one opened on an old one. -
ThreadUpdate(channel: channel.Channel) -
ThreadDelete( id: id.Id(id.Channel), guild_id: id.Id(id.Guild), parent_id: option.Option(id.Id(id.Channel)), type_: channel.ChannelType, )Four keys and no more. It looks like a channel and is not one, and the channel decoder would accept it: a channel needs only
idandtype. -
MessageCreate(message: message.Message)Without
MessageContent,content,embeds,attachmentsandcomponentsare empty unless the message mentions the bot or is a DM. -
MessageUpdate(message: message.MessageUpdate)Not a
Message. Discord sends only what changed, so everything butidandchannel_idis optional: an embed-only edit has no author. -
MessageDelete( id: id.Id(id.Message), channel_id: id.Id(id.Channel), guild_id: option.Option(id.Id(id.Guild)), ) -
MessageDeleteBulk( ids: List(id.Id(id.Message)), channel_id: id.Id(id.Channel), guild_id: option.Option(id.Id(id.Guild)), )Guild channels only. Discord does not list this one under DIRECT_MESSAGES, so a DM bot relying on it to clean up never gets it.
-
MessageReactionAdd(ReactionAdd) -
MessageReactionRemove(ReactionRemove)Not a mirror of the add. See
ReactionRemove. -
MessageReactionRemoveAll( channel_id: id.Id(id.Channel), message_id: id.Id(id.Message), guild_id: option.Option(id.Id(id.Guild)), )Every reaction on the message is gone.
-
MessageReactionRemoveEmoji( channel_id: id.Id(id.Channel), message_id: id.Id(id.Message), guild_id: option.Option(id.Id(id.Guild)), emoji: emoji.Emoji, )Every reaction of one emoji is gone.
-
InteractionCreate(interaction: interaction.Interaction)Ungated: a bot with no intents still gets every interaction.
-
TypingStartEvent(TypingStart) -
UserUpdate(user: user.User)The bot’s own user changed. Not sent for anyone else.
-
VoiceStateUpdate(voice_state.VoiceState)A null
channel_idon the state means the user left voice, and there is no separate event for that.voice_state.has_leftasks it directly. -
VoiceServerUpdate( token: String, guild_id: id.Id(id.Guild), endpoint: option.Option(String), )endpointisNonewhen the voice server went away. Disconnect and wait for the next one. Reconnecting to nothing loops. -
Raw(name: String, data: dynamic.Dynamic)A dispatch glyde did not model, or one whose
ddid not fit, withdintact.is_modelledtells those apart;dispatchgives the errors.
pub type MembersChunk {
MembersChunk(
guild_id: id.Id(id.Guild),
members: List(member.GuildMember),
chunk_index: Int,
chunk_count: Int,
not_found: List(String),
nonce: option.Option(String),
)
}
Constructors
-
MembersChunk( guild_id: id.Id(id.Guild), members: List(member.GuildMember), chunk_index: Int, chunk_count: Int, not_found: List(String), nonce: option.Option(String), )Arguments
- members
-
At most 1000. glyde does not reassemble the chunks: count them with
chunk_indexandchunk_count. - not_found
-
Echoes the request back, so this is whatever the caller sent, a real snowflake or not. A String, never an
Id. - nonce
-
Absent when the request’s nonce was over 32 bytes, which Discord drops silently.
The three things one dispatch can be. Malformed is a glyde bug or a
Discord schema change: report it. Unmodelled is ordinary.
pub type Outcome {
Decoded(Event)
Unmodelled
Malformed(errors: List(decode.DecodeError))
}
Constructors
-
Decoded(Event) -
Unmodelled -
Malformed(errors: List(decode.DecodeError))
Keyed by opcode, and open. Only opcode 8’s shape is documented, and meta
can be missing altogether.
pub type RateLimitMeta {
MemberRequestMeta(
guild_id: id.Id(id.Guild),
nonce: option.Option(String),
)
UnknownMeta(opcode: Int, raw: dynamic.Dynamic)
}
Constructors
-
MemberRequestMeta( guild_id: id.Id(id.Guild), nonce: option.Option(String), ) -
UnknownMeta(opcode: Int, raw: dynamic.Dynamic)Any other opcode, and any opcode 8 whose meta had no
guild_id.rawis the meta object as sent, or null when there was none.
The answer to going over a gateway send limit, today one REQUEST_GUILD_MEMBERS per guild per 30 seconds.
pub type RateLimited {
RateLimited(
opcode: Int,
retry_after: Float,
meta: RateLimitMeta,
)
}
Constructors
-
RateLimited(opcode: Int, retry_after: Float, meta: RateLimitMeta)Arguments
- opcode
-
The send opcode that was limited. 8 today.
- retry_after
-
SECONDS, fractional, and a bare
30when the value is whole.retry_after_msis usually what you want.
Not a mirror of ReactionRemove: the add carries member,
message_author_id and burst_colors, and the remove carries none.
pub type ReactionAdd {
ReactionAdd(
user_id: id.Id(id.User),
channel_id: id.Id(id.Channel),
message_id: id.Id(id.Message),
guild_id: option.Option(id.Id(id.Guild)),
member: option.Option(member.GuildMember),
emoji: emoji.Emoji,
message_author_id: option.Option(id.Id(id.User)),
burst: Bool,
burst_colors: List(String),
type_: message.ReactionType,
)
}
Constructors
-
ReactionAdd( user_id: id.Id(id.User), channel_id: id.Id(id.Channel), message_id: id.Id(id.Message), guild_id: option.Option(id.Id(id.Guild)), member: option.Option(member.GuildMember), emoji: emoji.Emoji, message_author_id: option.Option(id.Id(id.User)), burst: Bool, burst_colors: List(String), type_: message.ReactionType, )Arguments
- member
-
Present on a guild reaction, absent in a DM.
- emoji
-
Partial:
idandnameonly, andidis null for a unicode emoji. - message_author_id
-
Who wrote the message being reacted to. Absent on an older payload.
- burst_colors
-
Hex colours of the burst animation, as sent.
Three fields fewer than ReactionAdd, which is Discord’s shape.
pub type ReactionRemove {
ReactionRemove(
user_id: id.Id(id.User),
channel_id: id.Id(id.Channel),
message_id: id.Id(id.Message),
guild_id: option.Option(id.Id(id.Guild)),
emoji: emoji.Emoji,
burst: Bool,
type_: message.ReactionType,
)
}
Constructors
-
ReactionRemove( user_id: id.Id(id.User), channel_id: id.Id(id.Channel), message_id: id.Id(id.Message), guild_id: option.Option(id.Id(id.Guild)), emoji: emoji.Emoji, burst: Bool, type_: message.ReactionType, )
pub type TypingStart {
TypingStart(
channel_id: id.Id(id.Channel),
guild_id: option.Option(id.Id(id.Guild)),
user_id: id.Id(id.User),
timestamp: Int,
member: option.Option(member.GuildMember),
)
}
Constructors
-
TypingStart( channel_id: id.Id(id.Channel), guild_id: option.Option(id.Id(id.Guild)), user_id: id.Id(id.User), timestamp: Int, member: option.Option(member.GuildMember), )Arguments
- timestamp
-
UNIX SECONDS. Not milliseconds and not ISO-8601, and the only unix timestamp anywhere in the dispatch surface.
- member
-
Present on a guild channel, absent in a DM.
Values
pub fn dispatch(name: String, data: dynamic.Dynamic) -> Dispatch
Decode one dispatch, keeping d and saying which of the three happened.
Total.
pub fn is_modelled(name: String) -> Bool
The question to ask about a Raw: an unmodelled name is ordinary, a
modelled one that came back Raw is a payload that did not fit.
pub fn name(event: Event) -> String
The wire t an event came from. The inverse of decoder_for’s table: a
name added to one and not the other silently drops an event.
pub fn retry_after_ms(limited: RateLimited) -> Int
retry_after in milliseconds, never negative: “retry now” is the only
reading of a negative delay a host can act on. Our floor, not Discord’s.