From 43b85f4e039af3c1633c05be59103aeaee9033d5 Mon Sep 17 00:00:00 2001 From: Skillz Date: Mon, 27 Apr 2020 11:48:49 -0400 Subject: [PATCH] more cleanup --- README.md | 3 +- module/client.ts | 70 ++++++++++++++++--------------------------- module/gateway.ts | 2 +- types/discord.ts | 21 ++++++++----- types/message-type.ts | 6 ---- 5 files changed, 43 insertions(+), 59 deletions(-) delete mode 100644 types/message-type.ts diff --git a/README.md b/README.md index 9c8f0a5a2..90659eed2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Discordeno + Discord API library wrapper in Deno -[Discord Server](https://discord.gg/rFJzqex) +[Discord Server](https://discord.gg/J4NqJ72) ## Bot Boilerplate Template / Frameworks diff --git a/module/client.ts b/module/client.ts index cdb98f4da..959ad9d98 100644 --- a/module/client.ts +++ b/module/client.ts @@ -8,12 +8,12 @@ import { Presence_Update_Payload, Typing_Start_Payload, Voice_State_Update_Payload, + ReadyPayload, } from "../types/discord.ts" // import { spawnShards } from "./sharding_manager.ts" import { connectWebSocket, isWebSocketCloseEvent, WebSocket } from "https://deno.land/std@v0.41.0/ws/mod.ts" import { Client_Options, Event_Handlers } from "../types/options.ts" -import { CollectedMessageType } from "../types/message-type.ts" -import { send_constant_heartbeats, update_previous_sequence_number } from "./gateway.ts" +import { send_constant_heartbeats, update_previous_sequence_number, previous_sequence_number } from "./gateway.ts" import { create_guild } from "../structures/guild.ts" import { handle_internal_guild_create, @@ -67,6 +67,8 @@ export let authorization = "" export let bot_id = "" /** The bot's token. This should never be used by end users. It is meant to be used internally to make requests to the Discord API. */ export let token = "" +/** The session id is needed for RESUME functionality when discord disconnects randomly. */ +export let sessionID = "" export let event_handlers: Event_Handlers = {} export const create_client = async (data: Client_Options) => { @@ -83,8 +85,7 @@ export const create_client = async (data: Client_Options) => { // Initial API connection to get info about bots connection const bot_gateway_data = (await Request_Manager.get(endpoints.GATEWAY_BOT)) as DiscordBotGatewayData - const socket = await connectWebSocket(bot_gateway_data.url) - collect_messages(socket) + let socket = await connectWebSocket(bot_gateway_data.url) const payload = { token: data.token, @@ -97,50 +98,30 @@ export const create_client = async (data: Client_Options) => { // Intial identify with the gateway await socket.send(JSON.stringify({ op: GatewayOpcode.Identify, d: payload })) - for await (const _message of connect(socket)) { + for await (const message of socket.receive()) { + if (typeof message === "string") { + handle_discord_payload(JSON.parse(message), socket) + } else if (isWebSocketCloseEvent(message)) { + logRed(`Close :( ${message}`) + // RESUME: Websocket closed/disconnected so we should try and resume the connection. + socket = await connectWebSocket(bot_gateway_data.url) + + await socket.send( + JSON.stringify({ + op: GatewayOpcode.Resume, + d: { + token: data.token, + session_id: sessionID, + seq: previous_sequence_number, + }, + }) + ) + } } // spawnShards(bot_gateway_data, 1, socket, payload) } -async function* collect_messages(socket: WebSocket) { - for await (const message of socket.receive()) { - if (typeof message === "string") { - yield { - type: CollectedMessageType.Message, - data: JSON.parse(message), - } - } else if (isWebSocketCloseEvent(message)) { - yield { type: CollectedMessageType.Close, ...message } - return - } - } -} - -/** Begins initial handshake, creates the websocket with Discord and spawns all necessary shards. */ -async function* connect(socket: WebSocket) { - for await (const message of collect_messages(socket)) { - switch (message.type) { - case CollectedMessageType.Ping: - logRed("Ping!") - yield message - break - case CollectedMessageType.Pong: - logRed("Pong!") - yield message - break - case CollectedMessageType.Close: - logRed(`Close :( ${message}`) - yield message - break - case CollectedMessageType.Message: - handle_discord_payload(message.data, socket) - yield message - break - } - } -} - function handle_discord_payload(data: DiscordPayload, socket: WebSocket) { // Update the sequence number if it is present so that heartbeating can be accurate if (data.s) update_previous_sequence_number(data.s) @@ -153,11 +134,12 @@ function handle_discord_payload(data: DiscordPayload, socket: WebSocket) { // Incase the user wants to listen to heartbeat responses return event_handlers.heartbeat?.() case GatewayOpcode.Reconnect: + case GatewayOpcode.InvalidSession: // TODO: Reconnect to the gateway https://discordapp.com/developers/docs/topics/gateway#reconnect return case GatewayOpcode.Dispatch: if (data.t === "READY") { - console.debug(data) + sessionID = (data.d as ReadyPayload).session_id return event_handlers.ready?.() } if (data.t === "CHANNEL_CREATE") return handle_internal_channel_create(data.d as Channel_Create_Payload) diff --git a/module/gateway.ts b/module/gateway.ts index e25090e18..44aa712e7 100644 --- a/module/gateway.ts +++ b/module/gateway.ts @@ -3,7 +3,7 @@ import { GatewayOpcode } from "../types/discord.ts" import { delay } from "https://deno.land/std@v0.41.0/util/async.ts" // Discord requests null if no number has yet been sent by discord -let previous_sequence_number: number | null = null +export let previous_sequence_number: number | null = null // TODO: If a client does not receive a heartbeat ack between its attempts at sending heartbeats, it should immediately terminate the connection with a non-1000 close code, reconnect, and attempt to resume. export const send_constant_heartbeats = async (socket: WebSocket, interval: number) => { diff --git a/types/discord.ts b/types/discord.ts index 1df95dd0c..c0e7e8664 100644 --- a/types/discord.ts +++ b/types/discord.ts @@ -45,7 +45,7 @@ export enum GatewayOpcode { RequestGuildMembers, InvalidSession, Hello, - HeartbeatACK + HeartbeatACK, } export enum GatewayCloseEventCode { @@ -59,7 +59,7 @@ export enum GatewayCloseEventCode { RateLimited, SessionTimeout, InvalidShard, - ShardingRequired + ShardingRequired, } export enum VoiceOpcode { @@ -73,7 +73,7 @@ export enum VoiceOpcode { Resume, Hello, Resumed, - ClientDisconnect = 13 + ClientDisconnect = 13, } export enum VoiceCloseEventCode { @@ -87,7 +87,7 @@ export enum VoiceCloseEventCode { UnknownProtocol, Disconnected = 4014, VoiceServerCrashed, - UnknownEncryptionMode + UnknownEncryptionMode, } export enum HttpResponseCode { @@ -101,7 +101,7 @@ export enum HttpResponseCode { NotFound, MethodNotAllowed, TooManyRequests = 429, - GatewayUnavailable = 502 + GatewayUnavailable = 502, // ServerError left untyped because it's 5xx. } @@ -157,7 +157,7 @@ export enum JSONErrorCode { InviteAcceptedToGuildApplicationBotNotIn, InvalidAPIVersion = 50041, ReactionBlocked = 90001, - ResourceOverloaded = 130000 + ResourceOverloaded = 130000, } export interface Properties { @@ -182,7 +182,7 @@ export enum StatusType { DoNotDisturb = "dnd", Idle = "idle", Invisible = "invisible", - Offline = "offline" + Offline = "offline", } export type Status_Type = "online" | "dnd" | "idle" | "invisible" | "offline" @@ -255,3 +255,10 @@ export interface Voice_State_Update_Payload { /** Whether this user is muted by the bot */ suppress: boolean } + +export interface ReadyPayload { + /** used for resuming connections */ + session_id: string + /** (shard_id, num_shards) the shard information associated with this session, if sent when identifying */ + shard?: [number, number] +} diff --git a/types/message-type.ts b/types/message-type.ts deleted file mode 100644 index 664478ebb..000000000 --- a/types/message-type.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum CollectedMessageType { - Ping, - Pong, - Close, - Message -}