Lingo.Gateway (Lingo v0.4.2)

Copy Markdown View Source

The bot's gateway connection: presence, requests sent over the gateway, and its shards.

Answers to gateway requests arrive later, as events for Lingo.Bot.handle_event/2.

Large bots

  • Resuming. A shard that crashes or restarts resumes its session instead of identifying again, so no events are lost and no identify is spent. To carry sessions over a full restart (a redeploy), give a Lingo.Gateway.SessionStore.
  • Identify limits. Shards identify in their max_concurrency bucket order, and never past the daily limit (going over it resets the token): once it's spent they wait for it to reset.
  • Resharding without downtime. reshard/0, or auto_reshard: true under :sharding, brings up the new shards next to the running ones, and switches over once all are ready.
  • Several nodes. Run one bot on connected nodes by giving each the same :name and :count, and its own :ids. The nodes share the identify limits, and gateway requests reach the node that runs the guild's shard; update_presence/2 reaches every node. Shard statuses and latencies are this node's, and so are cache reads unless they ask for cluster: true (see Lingo.Cache).
  • Encoding and compression. gateway: [encoding: :etf] receives Erlang terms instead of JSON. compression: :zstd (OTP 28 or later) uses less CPU than the default :zlib; :none turns it off.

Lingo.Telemetry lists the events to watch all this with.

Summary

Functions

Waits until every shard on this node has connected once, for things to do once the bot is up

Every shard's last heartbeat round trip in ms.

A shard's last heartbeat round trip in ms, or nil.

Asks for extra channel info, like voice channel statuses. It arrives as :channel_info.

Asks for a guild's members. They arrive as :guild_members_chunk events and fill the cache. Options: :query, :limit, :presences, :user_ids and :nonce.

Asks for the soundboard sounds of some guilds. They arrive as :soundboard_sounds events.

Asks Discord for the recommended shard count and, when it differs, moves to it without downtime: the new shards connect next to the running ones, delivering nothing, and take over once all are ready (events at the very moment of the switch may arrive twice or not at all). The old shards then close. The new shards' :ready events aren't delivered either, and await_ready/1 doesn't wait for them.

Reconnects a shard from scratch, identifying again.

The sessions of the shards on this node, as a Lingo.Gateway.SessionStore saves them.

How many shards the bot runs in total.

The shard a guild's events arrive on.

The connection state of a shard, such as :connected, or :dead.

The connection state of every shard.

Sets the bot's status (:online, :idle, :dnd or :invisible) on every shard, with an optional text: or activity:.

Functions

await_ready(timeout \\ :infinity)

@spec await_ready(timeout()) :: :ok | {:error, :timeout}

Waits until every shard on this node has connected once, for things to do once the bot is up:

def handle_continue(:start, state) do
  :ok = Lingo.Gateway.await_ready()
  ...
end

Returns :ok right away when they already have, or when the bot runs without a gateway, and {:error, :timeout} if timeout (ms) runs out. It doesn't wait again for reconnects or a reshard. Each shard's own sessions are :ready and :resumed events.

Call it from handle_continue, as above, not from init: a supervisor starts its children one by one, so waiting in init holds up the rest of your application until every shard connects, minutes for a large bot. It covers this node's shards only, and exits if the bot's shard manager restarts while it waits.

latencies()

@spec latencies() :: %{required(non_neg_integer()) => non_neg_integer()}

Every shard's last heartbeat round trip in ms.

latency(shard_id)

@spec latency(non_neg_integer()) :: non_neg_integer() | nil

A shard's last heartbeat round trip in ms, or nil.

request_channel_info(guild_id, fields \\ ["status", "voice_start_time"])

@spec request_channel_info(String.t(), [String.t()]) :: :ok

Asks for extra channel info, like voice channel statuses. It arrives as :channel_info.

request_guild_members(guild_id, opts \\ [])

@spec request_guild_members(
  String.t(),
  keyword()
) :: :ok

Asks for a guild's members. They arrive as :guild_members_chunk events and fill the cache. Options: :query, :limit, :presences, :user_ids and :nonce.

request_soundboard_sounds(guild_ids)

@spec request_soundboard_sounds([String.t()]) :: :ok

Asks for the soundboard sounds of some guilds. They arrive as :soundboard_sounds events.

reshard()

@spec reshard() ::
  {:ok, %{from: pos_integer(), to: pos_integer()}}
  | :noop
  | {:error,
     :fixed_count
     | :fixed_ids
     | :in_progress
     | :not_started
     | :identify_limit
     | Lingo.Error.t()}

Asks Discord for the recommended shard count and, when it differs, moves to it without downtime: the new shards connect next to the running ones, delivering nothing, and take over once all are ready (events at the very moment of the switch may arrive twice or not at all). The old shards then close. The new shards' :ready events aren't delivered either, and await_ready/1 doesn't wait for them.

Returns once the reshard has started, with {:ok, %{from: old, to: new}}; the [:lingo, :gateway, :reshard, :stop] telemetry event says when the new shards took over. A reshard whose shards don't all get ready in time is abandoned. Otherwise it returns:

  • :noop - Discord recommends the count already running
  • {:error, :fixed_count} or {:error, :fixed_ids} - the :sharding option fixes them; a bot split across nodes always does
  • {:error, :in_progress} - a reshard is already under way
  • {:error, :not_started} - the shards haven't started yet
  • {:error, :identify_limit} - the day's identifies wouldn't cover the new shards
  • {:error, %Lingo.Error{}} - asking Discord failed

restart_shard(shard_id)

@spec restart_shard(non_neg_integer()) :: :ok | {:error, term()}

Reconnects a shard from scratch, identifying again.

sessions()

@spec sessions() :: [Lingo.Gateway.SessionStore.session()]

The sessions of the shards on this node, as a Lingo.Gateway.SessionStore saves them.

shard_count()

@spec shard_count() :: non_neg_integer()

How many shards the bot runs in total.

shard_for_guild(guild_id)

@spec shard_for_guild(String.t()) :: non_neg_integer()

The shard a guild's events arrive on.

shard_status(shard_id)

@spec shard_status(non_neg_integer()) :: atom()

The connection state of a shard, such as :connected, or :dead.

shard_statuses()

@spec shard_statuses() :: %{required(non_neg_integer()) => atom()}

The connection state of every shard.

update_presence(status, opts \\ [])

@spec update_presence(
  atom(),
  keyword()
) :: :ok

Sets the bot's status (:online, :idle, :dnd or :invisible) on every shard, with an optional text: or activity:.