discord | | discord gateway | Search

The code establishes and manages a WebSocket connection to Discord, handling events such as connection opening, message receiving, and connection closing, with functions like authorizeGateway() and closeGateway(). It also includes functions like requestAuthQ() for making requests to the Discord API, although this function appears to be incomplete and intended to handle a queue of requests.

Run example

npm run import -- "discord request"

discord request

const {request} = require('gaxios')
const WebSocket = require('ws')
const {delay, wait, timeout} = importer.import("discord utilities")
const {
  gatewayIdentified, gatewayClose, gatewayMessage,
} = importer.import("discord gateway")
const {
  TOKEN, DEFAULT_API, DEFAULT_RATE
} = importer.import("discord configuration")

var ws = false
var wsConnecting = false
var previousRequest = 0

async function gatewayUrl() {
  // TODO: return the same result if queried less than 1 second ago
  // doesn't use requestAuthQ because that would create an infinite loop
  var result = await request({
    headers: {
      'Authorization': `Bot ${TOKEN}`
    },
    method: 'GET',
    url: `${DEFAULT_API}gateway/bot`
  })
  return result.data
}

function gatewayOpen() {
  console.log('Connecting to Discord')
}

async function authorizeGateway() {
  if(wsConnecting) {
    var result = await wait(() => ws && ws.identified, 3000)
    if(!result)
      return await authorizeGateway()
    else
      return ws
  } else if (ws && ws.readyState == 1 && ws.identified) {
    return ws
  }

  wsConnecting = true
  try {
    ws = new WebSocket((await gatewayUrl()).url)
    ws.identified = false
  } catch (e) {
    console.log('Authorize error', e.message)
    ws = false
    wsConnecting = false
    return
  }
  ws.on('open', gatewayOpen)
  ws.on('message', gatewayMessage.bind(null, ws, authorizeGateway, interactionResponse))
  ws.on('close', gatewayClose.bind(null, ws, authorizeGateway))
  await wait(() => ws.identified, 3000)
  wsConnecting = false
  return ws
}

function closeGateway() {
  gatewayClose(ws)
}

var previousRequest = 0

async function requestAuthQ(outgoing) {
  await authorizeGateway()
  if(typeof outgoing.headers == 'undefined')
    outgoing.headers = {}
  outgoing.headers['Authorization'] = `Bot ${TOKEN}`
  outgoing.url = DEFAULT_API + outgoing.url
  previousRequest = await delay(previousRequest, DEFAULT_RATE)
  var resolveRequest
  resolveRequest = async () => {
    var result
    try {
      //console.log('Discord request', outgoing)
      result = (await request(outgoing)).data
    } catch (e) {
      // check result for rate limit and re-run this request in a queue
      if(e.code == 429) {
        console.log('Delayed request', e.response.data.retry_after)
        await timeout(e.response.data.retry_after * 1000)
        return await resolveRequest()
      } else {
        console.log(e)
        if(e.response) {
          console.log(e.response.data.errors)
        }
        throw e
      }
    }
    return result
  }
  return await resolveRequest()
}

async function interactionResponse(interactionId, interactionToken) {
  var json = {
    'type': 5
  }
  return await requestAuthQ({
    headers: {
      'Content-Type': 'application/json'
    },
    method: 'POST',
    url: `interactions/${interactionId}/${interactionToken}/callback`,
    data: JSON.stringify(json)
  })
}

module.exports = {
  authorizeGateway,
  gatewayUrl,
  closeGateway,
  requestAuthQ,
  interactionResponse,
}



What the code could have been:

const { request } = require('gaxios');
const WebSocket = require('ws');
const { delay, wait, timeout } = require('discord utilities');
const {
  gatewayIdentified, gatewayClose, gatewayMessage,
} = require('discord gateway');
const {
  TOKEN, DEFAULT_API, DEFAULT_RATE
} = require('discord configuration');

class DiscordGateway {
  constructor() {
    this.ws = false;
    this.wsConnecting = false;
    this.previousRequest = 0;
  }

  async gatewayUrl() {
    try {
      const result = await request({
        headers: {
          Authorization: `Bot ${TOKEN}`,
        },
        method: 'GET',
        url: `${DEFAULT_API}gateway/bot`,
      });
      return result.data;
    } catch (error) {
      console.error('Error getting gateway URL:', error);
      throw error;
    }
  }

  async authorizeGateway() {
    const now = Date.now();
    if (this.wsConnecting && now - this.previousRequest < 1000) {
      // Return cached result from previous request if queried less than 1 second ago
      return this.ws;
    }

    this.wsConnecting = true;
    try {
      const result = await this.gatewayUrl();
      this.ws = new WebSocket(result.url);
      this.ws.identified = false;
    } catch (error) {
      console.error('Authorize error:', error.message);
      this.ws = false;
      this.wsConnecting = false;
      return;
    }

    this.ws.on('open', () => console.log('Connecting to Discord'));
    this.ws.on('message', (event) => gatewayMessage(event, this.authorizeGateway));
    this.ws.on('close', () => gatewayClose(this.ws, this.authorizeGateway));
    await this.waitForGatewayIdentification(3000);
    this.wsConnecting = false;
    return this.ws;
  }

  async waitForGatewayIdentification(timeoutMs) {
    if (this.ws && this.ws.readyState === 1 && this.ws.identified) {
      return this.ws;
    }

    try {
      await wait(() => this.ws.identified, timeoutMs);
    } catch (error) {
      console.error('Error waiting for gateway identification:', error);
      throw error;
    }
  }

  async closeGateway() {
    gatewayClose(this.ws, this.authorizeGateway);
  }

  async requestAuthQ(outgoing) {
    await this.authorizeGateway();
    if (!outgoing.headers) {
      outgoing.headers = {};
    }
    outgoing.headers.Authorization = `Bot ${TOKEN}`;
    outgoing.url = DEFAULT_API + outgoing.url;
    const waitTime = await delay(this.previousRequest, DEFAULT_RATE);
    this.previousRequest = waitTime;

    const resolveRequest = async () => {
      try {
        const result = await request(outgoing);
        return result.data;
      } catch (error) {
        if (error.code === 429) {
          console.log('Delayed request:', error.response.data.retry_after);
          await timeout(error.response.data.retry_after * 1000);
          return await resolveRequest();
        } else {
          console.error(error);
          if (error.response) {
            console.error(error.response.data.errors);
          }
          throw error;
        }
      }
    };

    return await resolveRequest();
  }

  async interactionResponse(interactionId, interactionToken) {
    const json = { type: 5 };
    return await this.requestAuthQ({
      headers: {
        'Content-Type': 'application/json',
      },
      method: 'POST',
      url: `interactions/${interactionId}/${interactionToken}/callback`,
      data: JSON.stringify(json),
    });
  }
}

module.exports = new DiscordGateway();

Code Breakdown

Importing Modules and Variables

The code imports various modules and variables:

Variables are declared for the WebSocket connection (ws and wsConnecting) and for tracking the timing of requests (previousRequest).

Functions

gatewayUrl()

This function retrieves the Discord gateway URL by making a GET request to the Discord API with the bot token as an authorization header. The result is returned as a promise.

gatewayOpen()

This function logs a message to the console indicating that the connection to Discord is being established.

authorizeGateway()

This function is responsible for establishing a WebSocket connection to Discord and authorizing the bot. It:

  1. Checks if a connection is already being attempted (wsConnecting) or if a connection already exists (ws).
  2. If a connection is not present, creates a new WebSocket object using the gateway URL obtained from gatewayUrl().
  3. Sets up event listeners for the WebSocket connection:
  4. Waits for the connection to be fully established (identified by Discord) before returning the connection object.

closeGateway()

This function closes the WebSocket connection to Discord.

requestAuthQ()

This function makes a request to the Discord API with authentication. It:

  1. Calls authorizeGateway() to establish a new connection if necessary.
  2. Sets up the request headers and URL.
  3. Waits for the previous request to complete before sending the new request.
  4. Makes the request and returns the response as a promise.

Note that this function seems to be incomplete, as indicated by the TODO comment and the presence of a resolveRequest function. The code appears to be intended to handle a queue of requests, but this part is not fully implemented.