glyde/gateway/command

The gateway commands a host may send.

IDENTIFY, RESUME and HEARTBEAT live in glyde/gateway/frame: a host that sends one by hand can tear down a session the state machine is holding.

Types

Something to send on the socket. Each variant is exactly one opcode’s d.

pub type Command {
  UpdatePresence(presence: presence.Presence)
  UpdateVoiceState(
    guild: id.Id(id.Guild),
    channel: option.Option(id.Id(id.Channel)),
    self_mute: Bool,
    self_deaf: Bool,
  )
  RequestGuildMembers(request: MemberRequest)
}

Constructors

  • UpdatePresence(presence: presence.Presence)

    op 3.

  • UpdateVoiceState(
      guild: id.Id(id.Guild),
      channel: option.Option(id.Id(id.Channel)),
      self_mute: Bool,
      self_deaf: Bool,
    )

    op 4. glyde speaks no voice protocol; a third-party voice stack needs it.

    Arguments

    channel

    None disconnects from voice in this guild.

  • RequestGuildMembers(request: MemberRequest)

    op 8. Answers arrive as GUILD_MEMBERS_CHUNK dispatches of up to 1000 members each, handed over as they come rather than reassembled.

How many members a ByPrefix may come back with, min_limit to max_limit. Opaque so the 0 that means “every member” cannot be written as a search, and so a number Discord will not honour cannot be written at all.

pub opaque type Limit

Why limit refused. Both bounds are Discord’s, and neither comes back as an error: a 0 asks for something else, and a 500 is answered with 100.

pub type LimitError {
  LimitTooSmall(value: Int)
  LimitTooLarge(value: Int)
}

Constructors

  • LimitTooSmall(value: Int)

    Under min_limit. A search that may match nothing is not a search, and the 0 that asks for every member is AllMembers.

  • LimitTooLarge(value: Int)

    Over max_limit. Discord stops at 100 matches, so the rest of the number is members the host would wait for and never be sent.

Which members to ask for. Discord takes a name prefix, a list of ids, or the whole list, and each of the three is a different set of keys.

Every variant takes a nonce, which comes back on every chunk and is what matches an answer to the request for it. The chunk carries it as a plain string, so compare with nonce_to_string.

pub type MemberRequest {
  AllMembers(guild: id.Id(id.Guild), nonce: option.Option(Nonce))
  ByPrefix(
    guild: id.Id(id.Guild),
    prefix: String,
    limit: Limit,
    nonce: option.Option(Nonce),
  )
  ByIds(
    guild: id.Id(id.Guild),
    users: UserIds,
    nonce: option.Option(Nonce),
  )
}

Constructors

  • AllMembers(guild: id.Id(id.Guild), nonce: option.Option(Nonce))

    Every member of the guild. This is the one Discord requires the GUILD_MEMBERS intent for, and that intent is privileged: an app has to be approved for it before the answer arrives.

  • ByPrefix(
      guild: id.Id(id.Guild),
      prefix: String,
      limit: Limit,
      nonce: option.Option(Nonce),
    )

    Members whose name starts with prefix, at most limit of them.

  • ByIds(
      guild: id.Id(id.Guild),
      users: UserIds,
      nonce: option.Option(Nonce),
    )

    Specific members, by id, built with user_ids.

A nonce Discord will echo. Opaque because the 32-byte bound is the whole point: a longer one is not rejected, it is silently dropped.

pub opaque type Nonce

Why nonce refused. Discord’s bound, and it fails quietly on the wire, which is why it is checked here.

pub type NonceError {
  NonceTooLong(bytes: Int)
}

Constructors

  • NonceTooLong(bytes: Int)

    Over max_nonce_bytes. Discord drops a longer nonce and answers the chunks without one, so nothing can match them to the request again.

The ids one ByIds asks about, at most max_user_ids of them. Opaque for the same reason as Nonce: a host with more has to send more requests, and only the constructor can say so.

pub opaque type UserIds

Why user_ids refused. Discord’s bound, and it fails quietly on the wire too.

pub type UserIdsError {
  TooManyUsers(count: Int)
}

Constructors

  • TooManyUsers(count: Int)

    Over max_user_ids ids in one request.

Values

pub fn encode(command: Command) -> frame.Outbound

Encode to a complete gateway payload.

pub fn limit(value: Int) -> Result(Limit, LimitError)

1 to 100. Refused rather than clamped: a host that wanted 500 members has to know it is only getting 100 of them.

pub fn nonce(text: String) -> Result(Nonce, NonceError)

Measured in bytes, because that is what Discord counts. A 32-character nonce with an accent in it is over the limit.

pub fn nonce_to_string(nonce: Nonce) -> String

The text back out. A member chunk echoes the nonce as a plain string, so this is how a host matches an answer to the request that asked for it.

pub fn user_ids(
  users: List(id.Id(id.User)),
) -> Result(UserIds, UserIdsError)

An empty list asks about nobody. Discord takes it, and glyde does not invent an id to fill it.

Search Document