Sending data to bots in real-time

The Gateway is Discord's way of sending real-time data to the client. This data can be used by bots to assist in running tasks. Learn from this article how the Gateway works, its design structure and how millions of bot applications use it.

Sending data to bots in real-time

Over the past 7 years on Discord, the Bot API has been used as a tool to help empower millions of bots across the platform to run. Part of the API, there is something called the "Gateway." This is Discord's way of communicating data to you in real-time as the client. In this article, we'll explain how the Gateway works and its importance in helping bots run.


The purpose of the Gateway.

The Gateway's purpose is to securely send data between the server and client over real-time. Most actions that ever occur from a bot rely on this data being received. Tools such as commands, components and logging heavily rely on a real-time communication. But how do they send this in real-time?

Well, Discord achieve that by using WebSockets, a type of Internet protocol designed for two-way communication between a host and user. In our example, the host would be the Gateway and user being a client, or a bot, that wishes to receive information from it.

What is being communicated?

The Gateway communicates data in the form of packets, a data structure in the form of bytes, given as a payload. Payloads are sets of data intended to be received from any given input. Now, "given input" for us is simply an action occurring by Discord meant for the client to see. We receive this data as what's called a byte stream, a sequence of bytes. The client's job is to deserialise this data into something much more human-readable. For a bot, that data is most commonly returned back in the form of an object.

Payloads are sent under signifiers, which we call events. Something's basically happened here, and we're providing information per "event" that can exist within the platform. Let's better understand what an event looks like by showing code written in Python to help visualise this data.

import typing

from attrs import define, field

@define()
class GatewayEvent:
    # "op" is short for an opcode, or better known as an
    # operation code. Opcodes are what the Gateway return to
    # us to clarify the internal code for an event.
    op: int = field()
    
    # "d" is short for data, what's actually meant for us to
    # use out of the event. This is the most important part of
    # the event payload, as this is what's returned back to 
    # Discord bots to use as objects.
    # An example is a message event returning back a Message object.
    d: typing.Optional[typing.Any] = field(default=None)
    
    # "s" is short for the "sequence number."
    # this is used for reconnections, and is important later on
    # if the bot were to ever die from a disconnect.
    s: typing.Optional[int] = field(default=None)
    
    # "t" is short for type, or the event type. This returns back
    # the internal name of the event. This is also important as it
    # provides bots a way a way to interface "listening" to a specific
    # event.
    t: typing.Optional[str] = field(default=None)
    
    # Below are some properties to help point to the abstracted 
    # and much more obfuscated attributes.
    
    @property
    def data(self) -> typing.Any | None:
        return self.d
        
    @property
    def sequence(self) -> int | None:
        return self.s
        
    @property
    def name(self) -> str | None:
        return self.t

How are Gateway events used?

An event can be used for almost anything. Depending on what kind of event you've got and the data returned for it, bots can tap into these events. These are typically referred to as "event listeners," and there are two kinds of events that can be given from the Gateway: dispatch events, and then connection events.

Receiving a dispatch event.

A dispatch event is any event that happens after a bot has fully connected to the Gateway. The term "dispatch" here is a way to describe data purposefully sent to the client. In particular, we'll be talking about events that can be triggered by actions.

Examples of events that can occur in this manner are:

  • Posting a message in a channel.
  • Bans happening from a community raid.
  • Creating a scheduled event for your community.

These events, however, can be restricted to the client from being able to receive, depending on what kind of intent you have.

What are intents?

Intents are a system to better allow the client to control how much data is returned from the Gateway based on which events. This helps allow developers to scale their applications for better performance. Depending on the intents given to a bot for connecting and identifying with, they can be restricted access to information.

To make better sense out of this, let's visualise them as a series of integer flags in Python. The goal of these flags are to act as bitwise operations and bit shift.

import enum

class IntentFlag(enum.IntFlag):
    GUILDS = 1 << 0
    GUILD_MEMBERS = 1 << 1
    GUILD_BANS = 1 << 2
    GUILD_EMOJIS_AND_STICKERS = 1 << 3
    GUILD_INTEGRATIONS = 1 << 4
    GUILD_WEBHOOKS = 1 << 5
    GUILD_INVITES = 1 << 6
    GUILD_VOICE_STATES = 1 << 7
    GUILD_PRESENCES = 1 << 8
    GUILD_MESSAGES = 1 << 9
    GUILD_MESSAGE_REACTIONS = 1 << 10
    GUILD_MESSAGE_TYPING = 1 << 11
    DIRECT_MESSAGES = 1 << 12
    DIRECT_MESSAGE_REACTIONS = 1 << 13
    DIRECT_MESSAGE_TYPING = 1 << 14
    MESSAGE_CONTENT = 1 << 15 # this one is important for bot commands!
    GUILD_SCHEDULED_EVENTS = 1 << 16

    PRIVILEGED = GUILD_PRESENCES | GUILD_MEMBERS | MESSAGE_CONTENT
    DEFAULT = (
        GUILDS
        | GUILD_BANS
        | GUILD_EMOJIS_AND_STICKERS
        ...
    )
    ALL = DEFAULT | PRIVILEGED

Gateway intents are designed with granularity in mind, allowing us to add multiple to one another, forming higher bitwise values. This can be used to tell the Gateway multiple events that the bot wants access to. The moral of the story: the less intents you have, the less of a computational burden your application has to bear.

Receiving a connection event.

A connection event is any event that the client receives after sending a Gateway command during connection:

(These are not to be confused with bot commands!)

  • Heartbeat used for keeping a bot connection alive with the Gateway.
  • Reconnect — tells a bot when to reconnect. (Dead/"zombified" connection, invalid connection details)
  • Invalid Session — in parallel with Reconnect, sent when a session has been invalidated by the server.
  • Hello — the first message sent to the client by the server providing data needed for connection.
  • Heartbeat ACK — verifies that the server received a heartbeat sent by the client.

The client will be expected at times to respond back to the Gateway with one of these commands, such as a Heartbeat. The client will send back a command formatted in a similar manner as the GatewayEvent object shown earlier, but to the needs of the specific operation in mind.


How does a bot use the Gateway?

If you're a bot developer using a library to connect to the Gateway, chances are, you don't need to do anything else than specify your bot's intents and a runner call. But for those out there who don't understand how to code a Discord bot, I'll be providing two explanations: one that goes over the technical, nitty-gritty on how it's coded, and then a flow-chart.

Explaining the connection process.

Discord does not have an intensively strict order of operations on how to connect to the Gateway. Below is a tree-chart visualising how a connection is typically made. This varies between bots and the practices their libraries use.

CONNECT TO WEBSOCKET SERVER
(LOOP)
|__ HELLO
|   |__ STORE HEARTBEAT INTERVAL
|   |__ BEGIN HEARTBEATER
|   |__ IDENTIFY
|__ READY
|   |__ STORE SESSION_ID
|   |__ STORE SEQUENCE
|__ DISPATCH
|   |__ DISPATCH EVENT
|__ RECONNECT
|   |__ RESTART HEARTBEATER
|   |__ IDENTIFY
|   |__ RESUME
|__ INVALID SESSION
    |__ RESTART HEARTBEATER
    |__ IDENTIFY
    |__ RESUME

The connection to the Gateway starts with your client being "greeted" by the server with a Hello payload, containing an interval for your heartbeat. The Heartbeat is what's used to communicate to Discord that your bot intends to stay alive and continue receiving data. Imagine it like an anatomical heart: an organism needs a pulse in order to live.

After we receive this greeting, we begin our "heartbeater" which periodically sends these required heartbeats by when Discord ask of us. After this, we identify ourselves to Discord.

The Identify payload contains most importantly the bot's token and intents. Both of these are required.

Finally, we will receive a Ready event which contains some information about our application being connected to the Gateway, mainly the version of the API and the guilds the application (in this case, a bot) has access to. After this event is sent from the Gateway, a Dispatch event is able to appear.

In the event that our connection to the Gateway were to ever die, we check for if we're informed about it first with the Reconnect and Invalid Session events. If so, we'll attempt to re-identify and resume the last connection.

How bots process events.

Let's give an example of bot code written with the interactions.py Discord Python library to show how bots process events:

import interactions
import dotenv

client = interactions.Client(
    token=dotenv.get_key(".env", "token"),
    intents=interactions.Intents.GUILD_MEMBERS
)

@client.event
async def on_guild_member_add(member: interactions.GuildMember):
    print(f"{member.name} has joined a guild.")

@client.event
async def on_message_create(message: interactions.Message):
    if message.content.startswith("hello"):
        print("someone said hi!")

client.start()

Earlier, we discussed Gateway intents and how they work. This example above factors in that. Judging by our intents and names of our functions listed as events, we can assume:

  • We will get a print for the Guild Member Add event.
  • We will not get a print for the Message Create event.

A library will only hand back to the bot whatever you ask from the API. This bot in particular is only requesting for the Gateway to send us back Guild Member events if they ever happen.


How can I use the Gateway for better performance?

Since Gateway intents are designed to intentionally allow bot developers access to less or more data, (depending on the bot's needs) these points should be kept in mind:

  • Your bot should have intents only for what it needs.
  • Specifying a "default" list of intents is not a bad thing for small bots, but it isn't recommended for scaling.
  • More intents = more computing.

Not only is it important to have the right intents for your bot, you should also consider that each Discord library performs different. Research how your current library dependency performs, as well as others to find which one works best for you. For Python bot developers, there are plenty of libraries to choose from with varying results and different methodologies.

Understanding the Gateway is an important first step to understanding how bots receive most of their data. And as a bot developer, the ability to make an educated decision on how you continue to work with the Discord API and libraries.

Guest Author: Fl0w

We'd like to thank Flow for curating this article, explaining the esoterica of the Discord WebSocket protocol in an easy-to read way. If you'd like to write a guest article or join our content team, reach out to us on discord.gg/displace!