From 1b8161e85b7e97568ed9d5d4c394713b6fc1c1e0 Mon Sep 17 00:00:00 2001 From: Skillz4Killz <23035000+Skillz4Killz@users.noreply.github.com> Date: Sun, 4 Apr 2021 12:54:33 +0000 Subject: [PATCH] refactor: cleanup and fmt --- src/bot.ts | 1 - src/cache.ts | 46 +-- src/handlers/guilds/GUILD_CREATE.ts | 1 - src/handlers/guilds/GUILD_DELETE.ts | 1 - src/helpers/channels/create_channel.ts | 4 +- src/helpers/guilds/guild_banner_url.ts | 2 +- src/helpers/members/fetch_members.ts | 1 - src/helpers/members/get_member.ts | 9 +- src/helpers/members/get_members_by_query.ts | 1 - src/structures/guild.ts | 12 +- src/structures/message.ts | 42 +-- src/types/users/connection.ts | 2 +- src/util/constants.ts | 2 +- src/util/utils.ts | 1 - src/ws/README.md | 2 +- src/ws/cleanup_loading_shards.ts | 4 +- src/ws/create_shard.ts | 3 +- src/ws/handle_discord_payload.ts | 2 +- src/ws/handle_on_message.ts | 8 +- src/ws/heartbeat.ts | 2 +- src/ws/identify.ts | 2 +- src/ws/resume.ts | 2 +- src/ws/shard.ts | 383 -------------------- src/ws/start_gateway.ts | 10 +- src/ws/tell_cluster_to_identify.ts | 2 +- src/ws/ws.ts | 24 +- 26 files changed, 89 insertions(+), 480 deletions(-) delete mode 100644 src/ws/shard.ts diff --git a/src/bot.ts b/src/bot.ts index 12be79451..3507cfc47 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -2,7 +2,6 @@ import { getGatewayBot } from "./helpers/misc/get_gateway_bot.ts"; import { DiscordGatewayIntents } from "./types/gateway/gateway_intents.ts"; import { DiscordGetGatewayBot } from "./types/gateway/get_gateway_bot.ts"; import { baseEndpoints, GATEWAY_VERSION } from "./util/constants.ts"; -import { spawnShards } from "./ws/shard_manager.ts"; export let authorization = ""; export let secretKey = ""; diff --git a/src/cache.ts b/src/cache.ts index ed6193880..2bab9d370 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -23,7 +23,7 @@ export const cache = { ( value: | Collection - | PromiseLike> + | PromiseLike>, ) => void >(), executedSlashCommands: new Collection(), @@ -31,8 +31,8 @@ export const cache = { return new Collection( this.guilds.reduce( (a, b) => [...a, ...b.emojis.map((e) => [e.id, e])], - [] as any[] - ) + [] as any[], + ), ); }, }; @@ -78,32 +78,32 @@ export type TableName = function set( table: "guilds", key: string, - value: Guild + value: Guild, ): Promise>; function set( table: "channels", key: string, - value: Channel + value: Channel, ): Promise>; function set( table: "messages", key: string, - value: Message + value: Message, ): Promise>; function set( table: "members", key: string, - value: Member + value: Member, ): Promise>; function set( table: "presences", key: string, - value: PresenceUpdatePayload + value: PresenceUpdatePayload, ): Promise>; function set( table: "unavailableGuilds", key: string, - value: number + value: number, ): Promise>; async function set(table: TableName, key: string, value: any) { return cache[table].set(key, value); @@ -115,11 +115,11 @@ function get(table: "messages", key: string): Promise; function get(table: "members", key: string): Promise; function get( table: "presences", - key: string + key: string, ): Promise; function get( table: "unavailableGuilds", - key: string + key: string, ): Promise; async function get(table: TableName, key: string) { return cache[table].get(key); @@ -127,54 +127,54 @@ async function get(table: TableName, key: string) { function forEach( table: "guilds", - callback: (value: Guild, key: string, map: Map) => unknown + callback: (value: Guild, key: string, map: Map) => unknown, ): void; function forEach( table: "unavailableGuilds", - callback: (value: Guild, key: string, map: Map) => unknown + callback: (value: Guild, key: string, map: Map) => unknown, ): void; function forEach( table: "channels", - callback: (value: Channel, key: string, map: Map) => unknown + callback: (value: Channel, key: string, map: Map) => unknown, ): void; function forEach( table: "messages", - callback: (value: Message, key: string, map: Map) => unknown + callback: (value: Message, key: string, map: Map) => unknown, ): void; function forEach( table: "members", - callback: (value: Member, key: string, map: Map) => unknown + callback: (value: Member, key: string, map: Map) => unknown, ): void; function forEach( table: TableName, - callback: (value: any, key: string, map: Map) => unknown + callback: (value: any, key: string, map: Map) => unknown, ) { return cache[table].forEach(callback); } function filter( table: "guilds", - callback: (value: Guild, key: string) => boolean + callback: (value: Guild, key: string) => boolean, ): Promise>; function filter( table: "unavailableGuilds", - callback: (value: Guild, key: string) => boolean + callback: (value: Guild, key: string) => boolean, ): Promise>; function filter( table: "channels", - callback: (value: Channel, key: string) => boolean + callback: (value: Channel, key: string) => boolean, ): Promise>; function filter( table: "messages", - callback: (value: Message, key: string) => boolean + callback: (value: Message, key: string) => boolean, ): Promise>; function filter( table: "members", - callback: (value: Member, key: string) => boolean + callback: (value: Member, key: string) => boolean, ): Promise>; async function filter( table: TableName, - callback: (value: any, key: string) => boolean + callback: (value: any, key: string) => boolean, ) { return cache[table].filter(callback); } diff --git a/src/handlers/guilds/GUILD_CREATE.ts b/src/handlers/guilds/GUILD_CREATE.ts index d298b39a8..968388b9d 100644 --- a/src/handlers/guilds/GUILD_CREATE.ts +++ b/src/handlers/guilds/GUILD_CREATE.ts @@ -1,7 +1,6 @@ import { eventHandlers } from "../../bot.ts"; import { cache, cacheHandlers } from "../../cache.ts"; import { structures } from "../../structures/mod.ts"; -import { basicShards } from "../../ws/shard.ts"; import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import { DiscordGuild } from "../../types/guilds/guild.ts"; diff --git a/src/handlers/guilds/GUILD_DELETE.ts b/src/handlers/guilds/GUILD_DELETE.ts index a1ab0dd59..2ffd989e8 100644 --- a/src/handlers/guilds/GUILD_DELETE.ts +++ b/src/handlers/guilds/GUILD_DELETE.ts @@ -1,6 +1,5 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; -import { basicShards } from "../../ws/shard.ts"; import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import { DiscordUnavailableGuild } from "../../types/guilds/unavailable_guild.ts"; diff --git a/src/helpers/channels/create_channel.ts b/src/helpers/channels/create_channel.ts index 6292308fa..57ebc3106 100644 --- a/src/helpers/channels/create_channel.ts +++ b/src/helpers/channels/create_channel.ts @@ -15,7 +15,7 @@ import { export async function createChannel( guildId: string, name: string, - options?: CreateGuildChannel + options?: CreateGuildChannel, ) { const requiredPerms: Set = new Set(["MANAGE_CHANNELS"]); @@ -39,7 +39,7 @@ export async function createChannel( deny: calculateBits(perm.deny), })), type: options?.type || DiscordChannelTypes.GUILD_TEXT, - } + }, )) as DiscordChannel; const channelStruct = await structures.createChannelStruct(result); diff --git a/src/helpers/guilds/guild_banner_url.ts b/src/helpers/guilds/guild_banner_url.ts index 7b4866915..89e07efe4 100644 --- a/src/helpers/guilds/guild_banner_url.ts +++ b/src/helpers/guilds/guild_banner_url.ts @@ -8,7 +8,7 @@ export function guildBannerURL( id: string, banner: string, size: DiscordImageSize = 128, - format?: DiscordImageFormat + format?: DiscordImageFormat, ) { return banner ? formatImageURL(endpoints.GUILD_BANNER(id, banner), size, format) diff --git a/src/helpers/members/fetch_members.ts b/src/helpers/members/fetch_members.ts index f894a61dd..05c526d89 100644 --- a/src/helpers/members/fetch_members.ts +++ b/src/helpers/members/fetch_members.ts @@ -3,7 +3,6 @@ import { Member } from "../../structures/mod.ts"; import { DiscordGatewayIntents } from "../../types/gateway/gateway_intents.ts"; import { Errors } from "../../types/misc/errors.ts"; import { Collection } from "../../util/collection.ts"; -import { requestAllMembers } from "../../ws/shard_manager.ts"; /** * ⚠️ BEGINNER DEVS!! YOU SHOULD ALMOST NEVER NEED THIS AND YOU CAN GET FROM cache.members.get() diff --git a/src/helpers/members/get_member.ts b/src/helpers/members/get_member.ts index e98ca9320..52d529adb 100644 --- a/src/helpers/members/get_member.ts +++ b/src/helpers/members/get_member.ts @@ -15,11 +15,10 @@ export async function getMember( const guild = await cacheHandlers.get("guilds", guildId); if (!guild && !options?.force) return; - const data = - (await rest.runMethod( - "get", - endpoints.GUILD_MEMBER(guildId, id), - )) as MemberCreatePayload; + const data = (await rest.runMethod( + "get", + endpoints.GUILD_MEMBER(guildId, id), + )) as MemberCreatePayload; const memberStruct = await structures.createMemberStruct(data, guildId); await cacheHandlers.set("members", memberStruct.id, memberStruct); diff --git a/src/helpers/members/get_members_by_query.ts b/src/helpers/members/get_members_by_query.ts index 770f8147d..0750ca694 100644 --- a/src/helpers/members/get_members_by_query.ts +++ b/src/helpers/members/get_members_by_query.ts @@ -1,7 +1,6 @@ import { cacheHandlers } from "../../cache.ts"; import { Member } from "../../structures/mod.ts"; import { Collection } from "../../util/collection.ts"; -import { requestAllMembers } from "../../ws/shard_manager.ts"; /** Returns guild member objects for the specified user by their nickname/username. * diff --git a/src/structures/guild.ts b/src/structures/guild.ts index 2153aedc7..83ad2f66a 100644 --- a/src/structures/guild.ts +++ b/src/structures/guild.ts @@ -118,7 +118,7 @@ export async function createGuildStruct( } = snakeKeysToCamelCase(data) as Guild; const roles = await Promise.all( - data.roles.map((role) => structures.createRoleStruct(role)) + data.roles.map((role) => structures.createRoleStruct(role)), ); await Promise.all(channels.map(async (channel) => { @@ -148,7 +148,7 @@ export async function createGuildStruct( ), memberCount: createNewProp(memberCount), emojis: createNewProp( - new Collection(emojis.map((emoji) => [emoji.id ?? emoji.name, emoji])) + new Collection(emojis.map((emoji) => [emoji.id ?? emoji.name, emoji])), ), voiceStates: createNewProp( new Collection( @@ -165,11 +165,11 @@ export async function createGuildStruct( members.map(async (member) => { const memberStruct = await structures.createMemberStruct( member, - guild.id + guild.id, ); return cacheHandlers.set("members", memberStruct.id, memberStruct); - }) + }), ); } @@ -221,8 +221,8 @@ export interface GuildStruct extends size?: DiscordImageSize, format?: DiscordImageFormat, ): string | undefined; - /** The splash url for this server */ - splashURL( + /** The splash url for this server */ + splashURL( size?: DiscordImageSize, format?: DiscordImageFormat, ): string | undefined; diff --git a/src/structures/message.ts b/src/structures/message.ts index 328bebaf1..577b965c1 100644 --- a/src/structures/message.ts +++ b/src/structures/message.ts @@ -30,9 +30,8 @@ const baseMessage: Partial = { return this.member?.guilds.get(this.guildId); }, get link() { - return `https://discord.com/channels/${this.guildId || "@me"}/${ - this.channelId - }/${this.id}`; + return `https://discord.com/channels/${this.guildId || + "@me"}/${this.channelId}/${this.id}`; }, get mentionedRoles() { return this.mentionRoleIds?.map((id) => this.guild?.roles.get(id)) || []; @@ -61,20 +60,19 @@ const baseMessage: Partial = { return addReactions(this.channelId!, this.id!, reactions, ordered); }, reply(content) { - const contentWithMention = - typeof content === "string" - ? { - content, - mentions: { repliedUser: true }, - replyMessageId: this.id, - failReplyIfNotExists: false, - } - : { - ...content, - mentions: { ...(content.mentions || {}), repliedUser: true }, - replyMessageId: this.id, - failReplyIfNotExists: content.failReplyIfNotExists === true, - }; + const contentWithMention = typeof content === "string" + ? { + content, + mentions: { repliedUser: true }, + replyMessageId: this.id, + failReplyIfNotExists: false, + } + : { + ...content, + mentions: { ...(content.mentions || {}), repliedUser: true }, + replyMessageId: this.id, + failReplyIfNotExists: content.failReplyIfNotExists === true, + }; if (this.guildId) return sendMessage(this.channelId!, contentWithMention); return sendDirectMessage(this.author!.id, contentWithMention); @@ -132,8 +130,8 @@ export async function createMessageStruct(data: MessageCreateOptions) { } // Discord doesnt give guild id for getMessage() so this will fill it in - const guildIdFinal = - guildId || (await cacheHandlers.get("channels", channelId))?.guildId || ""; + const guildIdFinal = guildId || + (await cacheHandlers.get("channels", channelId))?.guildId || ""; const message = Object.create(baseMessage, { ...restProps, @@ -150,16 +148,16 @@ export async function createMessageStruct(data: MessageCreateOptions) { ...mentionChannelIds, // Add any other ids that can be validated in a channel mention format ...(rest.content.match(CHANNEL_MENTION_REGEX) || []).map((text) => - // converts the <#123> into 123 + // converts the <#123> into 123 text.substring(2, text.length - 1) ), - ].map((m) => m.id) + ].map((m) => m.id), ), webhookId: createNewProp(webhookId), messageReference: createNewProp(messageReference), timestamp: createNewProp(Date.parse(data.timestamp)), editedTimestamp: createNewProp( - editedTimestamp ? Date.parse(editedTimestamp) : undefined + editedTimestamp ? Date.parse(editedTimestamp) : undefined, ), }); diff --git a/src/types/users/connection.ts b/src/types/users/connection.ts index 13a78c17a..b86c6f4fb 100644 --- a/src/types/users/connection.ts +++ b/src/types/users/connection.ts @@ -1,6 +1,6 @@ import { SnakeCaseProps } from "../util.ts"; import { DiscordVisibilityTypes } from "./visibility_types.ts"; -import { Integration } from "../guilds/integration.ts" +import { Integration } from "../guilds/integration.ts"; export interface Connection { /** id of the connection account */ diff --git a/src/util/constants.ts b/src/util/constants.ts index d8701e5c1..28e0125c4 100644 --- a/src/util/constants.ts +++ b/src/util/constants.ts @@ -177,4 +177,4 @@ export const endpoints = { }; export const SLASH_COMMANDS_NAME_REGEX = /^[\w-]{1,32}$/; -export const CHANNEL_MENTION_REGEX = /<#[0-9]+>/g; \ No newline at end of file +export const CHANNEL_MENTION_REGEX = /<#[0-9]+>/g; diff --git a/src/util/utils.ts b/src/util/utils.ts index c27e75bcb..ec4ece452 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -3,7 +3,6 @@ import { DiscordGatewayOpcodes } from "../types/codes/gateway_opcodes.ts"; import { Errors } from "../types/misc/errors.ts"; import { DiscordImageFormat } from "../types/misc/image_format.ts"; import { DiscordImageSize } from "../types/misc/image_size.ts"; -import { basicShards, sendWS } from "../ws/shard.ts"; import { SLASH_COMMANDS_NAME_REGEX } from "./constants.ts"; export const sleep = (timeout: number) => { diff --git a/src/ws/README.md b/src/ws/README.md index 77e1bb7a5..b2088533b 100644 --- a/src/ws/README.md +++ b/src/ws/README.md @@ -201,4 +201,4 @@ export interface DiscordenoShard { intervalID: number; }; } -``` \ No newline at end of file +``` diff --git a/src/ws/cleanup_loading_shards.ts b/src/ws/cleanup_loading_shards.ts index 7b194e8b2..92ed6788d 100644 --- a/src/ws/cleanup_loading_shards.ts +++ b/src/ws/cleanup_loading_shards.ts @@ -9,13 +9,13 @@ export async function cleanupLoadingShards() { console.log( now > loadingShard.startedAt + 60000, now, - loadingShard.startedAt + loadingShard.startedAt, ); // Not a minute yet. Max should be few seconds but do a minute to be safe. if (now < loadingShard.startedAt + 60000) return; loadingShard.reject( - `[Identify Failure] Shard ${loadingShard.shardID} has not received READY event in over a minute.` + `[Identify Failure] Shard ${loadingShard.shardID} has not received READY event in over a minute.`, ); }); diff --git a/src/ws/create_shard.ts b/src/ws/create_shard.ts index 268a6da58..1f7efd446 100644 --- a/src/ws/create_shard.ts +++ b/src/ws/create_shard.ts @@ -1,5 +1,4 @@ import { identify } from "./identify.ts"; -import { handleOnMessage } from "./proxy/shard.ts"; import { resume } from "./resume.ts"; import { ws } from "./ws.ts"; @@ -29,7 +28,7 @@ export async function createShard(shardID: number) { case 4013: case 4014: throw new Error( - event.reason || "Discord gave no reason! GG! You broke Discord!" + event.reason || "Discord gave no reason! GG! You broke Discord!", ); // THESE ERRORS CAN NO BE RESUMED! THEY MUST RE-IDENTIFY! case 4003: diff --git a/src/ws/handle_discord_payload.ts b/src/ws/handle_discord_payload.ts index 80b7562ef..35490d79a 100644 --- a/src/ws/handle_discord_payload.ts +++ b/src/ws/handle_discord_payload.ts @@ -3,7 +3,7 @@ import { ws } from "./ws.ts"; /** Handler for processing all dispatch payloads that should be sent/forwarded to another server/vps/process. */ export async function handleDiscordPayload( data: DiscordPayload, - shardID: number + shardID: number, ) { await fetch(ws.url, { headers: { diff --git a/src/ws/handle_on_message.ts b/src/ws/handle_on_message.ts index aacdd4b57..3bf17ec13 100644 --- a/src/ws/handle_on_message.ts +++ b/src/ws/handle_on_message.ts @@ -13,8 +13,10 @@ export function handleOnMessage(message: any, shardID: number) { } if (message instanceof Uint8Array) { - message = decompressWith(message, 0, (slice: Uint8Array) => - ws.utf8decoder.decode(slice) + message = decompressWith( + message, + 0, + (slice: Uint8Array) => ws.utf8decoder.decode(slice), ); } @@ -27,7 +29,7 @@ export function handleOnMessage(message: any, shardID: number) { case DiscordGatewayOpcodes.Hello: ws.heartbeat( shardID, - (messageData.d as DiscordHeartbeat).heartbeat_interval + (messageData.d as DiscordHeartbeat).heartbeat_interval, ); break; case DiscordGatewayOpcodes.HeartbeatACK: diff --git a/src/ws/heartbeat.ts b/src/ws/heartbeat.ts index b9a399678..ab9122aee 100644 --- a/src/ws/heartbeat.ts +++ b/src/ws/heartbeat.ts @@ -34,7 +34,7 @@ export function heartbeat(shardID: number, interval: number) { JSON.stringify({ op: DiscordGatewayOpcodes.Heartbeat, d: currentShard.previousSequenceNumber, - }) + }), ); }, interval); } diff --git a/src/ws/identify.ts b/src/ws/identify.ts index 6b438debd..f020a3f2c 100644 --- a/src/ws/identify.ts +++ b/src/ws/identify.ts @@ -30,7 +30,7 @@ export async function identify(shardID: number, maxShards: number) { JSON.stringify({ op: DiscordGatewayOpcodes.Identify, d: { ...ws.identifyPayload, shard: [shardID, maxShards] }, - }) + }), ); }; diff --git a/src/ws/resume.ts b/src/ws/resume.ts index 6f0c204a1..78f03365b 100644 --- a/src/ws/resume.ts +++ b/src/ws/resume.ts @@ -48,7 +48,7 @@ export async function resume(shardID: number) { session_id: sessionID, seq: previousSequenceNumber, }, - }) + }), ); }; } diff --git a/src/ws/shard.ts b/src/ws/shard.ts deleted file mode 100644 index 2926d6710..000000000 --- a/src/ws/shard.ts +++ /dev/null @@ -1,383 +0,0 @@ -import { botGatewayData, eventHandlers, proxyWSURL } from "../bot.ts"; -import { DiscordGatewayOpcodes } from "../types/codes/gateway_opcodes.ts"; -import { Collection } from "../util/collection.ts"; -import { delay } from "../util/utils.ts"; -import { decompressWith } from "./deps.ts"; -import { handleDiscordPayload } from "./shard_manager.ts"; - -export const basicShards = new Collection(); -const heartbeating = new Map(); -const utf8decoder = new TextDecoder(); -const RequestMembersQueue: RequestMemberQueuedRequest[] = []; -let processQueue = false; - -export function createShard( - data: DiscordBotGatewayData, - identifyPayload: DiscordIdentify, - resuming = false, - shardId = 0, -) { - const oldShard = basicShards.get(shardId); - - const ws = new WebSocket(proxyWSURL); - ws.binaryType = "arraybuffer"; - const basicShard: BasicShard = { - id: shardId, - ws, - resumeInterval: 0, - sessionId: oldShard?.sessionId || "", - previousSequenceNumber: oldShard?.previousSequenceNumber || 0, - needToResume: false, - ready: false, - unavailableGuildIds: new Set(), - }; - - basicShards.set(basicShard.id, basicShard); - - ws.onopen = () => { - if (!resuming) { - // Initial identify with the gateway - identify(basicShard, identifyPayload); - } else { - resume(basicShard, identifyPayload); - } - }; - - ws.onerror = (errorEvent) => { - eventHandlers.debug?.({ - type: "wsError", - data: { shardId: basicShard.id, ...errorEvent }, - }); - }; - - ws.onmessage = async ({ data: message }) => { - if (message instanceof ArrayBuffer) { - message = new Uint8Array(message); - } - - if (message instanceof Uint8Array) { - message = decompressWith( - message, - 0, - (slice: Uint8Array) => utf8decoder.decode(slice), - ); - } - - if (typeof message === "string") { - const messageData = JSON.parse(message); - if (!messageData.t) eventHandlers.rawGateway?.(messageData); - switch (messageData.op) { - case DiscordGatewayOpcodes.Hello: - if (!heartbeating.has(basicShard.id)) { - await heartbeat( - basicShard, - (messageData.d as DiscordHello).heartbeat_interval, - identifyPayload, - data, - ); - } - break; - case DiscordGatewayOpcodes.HeartbeatACK: - heartbeating.set(shardId, true); - break; - case DiscordGatewayOpcodes.Reconnect: - eventHandlers.debug?.( - { type: "gatewayReconnect", data: { shardId: basicShard.id } }, - ); - basicShard.needToResume = true; - await resumeConnection(data, identifyPayload, basicShard.id); - break; - case DiscordGatewayOpcodes.InvalidSession: - eventHandlers.debug?.( - { - type: "gatewayInvalidSession", - data: { shardId: basicShard.id, data }, - }, - ); - // When d is false we need to reidentify - if (!messageData.d) { - createShard(data, identifyPayload, false, shardId); - break; - } - basicShard.needToResume = true; - await resumeConnection(data, identifyPayload, basicShard.id); - break; - default: - if (messageData.t === "RESUMED") { - eventHandlers.debug?.( - { type: "gatewayResumed", data: { shardId: basicShard.id } }, - ); - - basicShard.needToResume = false; - break; - } - // Important for RESUME - if (messageData.t === "READY") { - basicShard.sessionId = (messageData.d as ReadyPayload).session_id; - } - - // Update the sequence number if it is present - if (messageData.s) basicShard.previousSequenceNumber = messageData.s; - - await handleDiscordPayload(messageData, basicShard.id); - break; - } - } - }; - - ws.onclose = async ({ reason, code, wasClean }) => { - eventHandlers.debug?.( - { - type: "wsClose", - data: { shardId: basicShard.id, code, reason, wasClean }, - }, - ); - - if ([4001, 4002, 4004, 4005, 4010, 4011, 4012, 4013, 4014].includes(code)) { - throw new Error(reason); - } else if ([4000, 4003, 4007, 4008, 4009].includes(code)) { - eventHandlers.debug?.({ - type: "wsReconnect", - data: { shardId: basicShard.id, code, reason, wasClean }, - }); - createShard(data, identifyPayload, false, shardId); - } else if (code === 3069 && reason === "[discordeno] requested closure") { - return; - } else { - basicShard.needToResume = true; - await resumeConnection(botGatewayData, identifyPayload, shardId); - } - }; -} - -function identify(shard: BasicShard, payload: DiscordIdentify) { - eventHandlers.debug?.( - { - type: "gatewayIdentify", - data: { - shardId: shard.id, - }, - }, - ); - - sendWS({ - op: DiscordGatewayOpcodes.Identify, - d: { ...payload, shard: [shard.id, payload.shard[1]] }, - }, shard.id); -} - -function resume(shard: BasicShard, payload: DiscordIdentify) { - sendWS({ - op: DiscordGatewayOpcodes.Resume, - d: { - token: payload.token, - session_id: shard.sessionId, - seq: shard.previousSequenceNumber, - }, - }, shard.id); -} - -async function heartbeat( - shard: BasicShard, - interval: number, - payload: DiscordIdentify, - data: DiscordGetGatewayBot, -) { - // We lost socket connection between heartbeats, resume connection - if (shard.ws.readyState === WebSocket.CLOSED) { - shard.needToResume = true; - await resumeConnection(data, payload, shard.id); - heartbeating.delete(shard.id); - return; - } - - if (heartbeating.has(shard.id)) { - const receivedACK = heartbeating.get(shard.id); - // If a ACK response was not received since last heartbeat, issue invalid session close - if (!receivedACK) { - eventHandlers.debug?.( - { - type: "gatewayHeartbeatStopped", - data: { - interval, - previousSequenceNumber: shard.previousSequenceNumber, - shardId: shard.id, - }, - }, - ); - - return shard.ws.close(4009, "Session timed out"); - } - } - - // Set it to false as we are issuing a new heartbeat - heartbeating.set(shard.id, false); - - sendWS( - { op: DiscordGatewayOpcodes.Heartbeat, d: shard.previousSequenceNumber }, - shard.id, - ); - eventHandlers.debug?.( - { - type: "gatewayHeartbeat", - data: { - interval, - previousSequenceNumber: shard.previousSequenceNumber, - shardId: shard.id, - }, - }, - ); - await delay(interval); - await heartbeat(shard, interval, payload, data); -} - -async function resumeConnection( - data: DiscordGetGatewayBot, - payload: DiscordIdentify, - shardId: number, -) { - const shard = basicShards.get(shardId); - if (!shard) { - eventHandlers.debug?.( - { type: "missingShard", data: { shardId: shardId } }, - ); - return; - } - - if (!shard.needToResume) return; - - eventHandlers.debug?.({ type: "gatewayResume", data: { shardId: shard.id } }); - // Run it once - createShard(data, payload, true, shard.id); - // Then retry every 15 seconds - await delay(1000 * 15); - if (shard.needToResume) await resumeConnection(data, payload, shardId); -} - -export async function requestGuildMembers( - guildId: string, - shardId: number, - nonce: string, - options?: FetchMembersOptions, - queuedRequest = false, -) { - const shard = basicShards.get(shardId); - - // This request was not from this queue so we add it to queue first - if (!queuedRequest) { - RequestMembersQueue.push({ - guildId, - shardId, - nonce, - options, - }); - - if (!processQueue) { - processQueue = true; - return processGatewayQueue(); - } - return; - } - - // If its closed add back to queue to redo on resume - if (shard?.ws.readyState === WebSocket.CLOSED) { - await requestGuildMembers(guildId, shardId, nonce, options); - return; - } - - sendWS({ - op: DiscordGatewayOpcodes.RequestGuildMembers, - d: { - guild_id: guildId, - // If a query is provided use it, OR if a limit is NOT provided use "" - query: options?.query || (options?.limit ? undefined : ""), - limit: options?.limit || 0, - presences: options?.presences || false, - user_ids: options?.userIds, - nonce, - }, - }, shard?.id); -} - -async function processGatewayQueue() { - if (!RequestMembersQueue.length) { - processQueue = false; - return; - } - - await Promise.all(basicShards.map(async (shard) => { - const index = RequestMembersQueue.findIndex((q) => q.shardId === shard.id); - // 2 events per second is the rate limit. - const request = RequestMembersQueue[index]; - if (request) { - eventHandlers.debug?.( - { - type: "requestMembersProcessing", - data: { - remaining: RequestMembersQueue.length, - request, - }, - }, - ); - await requestGuildMembers( - request.guildId, - request.shardId, - request.nonce, - request.options, - true, - ); - // Remove item from queue - RequestMembersQueue.splice(index, 1); - - const secondIndex = RequestMembersQueue.findIndex((q) => - q.shardId === shard.id - ); - const secondRequest = RequestMembersQueue[secondIndex]; - if (secondRequest) { - eventHandlers.debug?.( - { - type: "requestMembersProcessing", - data: { - remaining: RequestMembersQueue.length, - request, - }, - }, - ); - await requestGuildMembers( - secondRequest.guildId, - secondRequest.shardId, - secondRequest.nonce, - secondRequest.options, - true, - ); - // Remove item from queue - RequestMembersQueue.splice(secondIndex, 1); - } - } - })); - - await delay(1500); - - await processGatewayQueue(); -} - -/** Enqueues the specified data to be transmitted to the server over the WebSocket connection, */ -export function sendWS(payload: DiscordGatewayPayload, shardId = 0) { - const shard = basicShards.get(shardId); - if (!shard) return false; - - const serialized = JSON.stringify(payload); - shard.ws.send(serialized); - - return true; -} - -/** Closes the WebSocket connection or connection attempt */ -export function closeWS(shardId = 0) { - const shard = basicShards.get(shardId); - if (!shard) return false; - - shard.ws.close(3069, "[discordeno] requested closure"); - - return true; -} diff --git a/src/ws/start_gateway.ts b/src/ws/start_gateway.ts index 6efbb9e94..72b3149ec 100644 --- a/src/ws/start_gateway.ts +++ b/src/ws/start_gateway.ts @@ -22,9 +22,13 @@ export async function startGateway(options: StartGatewayOptions) { setInterval(ws.resharder, 1000 * 60 * 60); ws.identifyPayload.intents = options.intents.reduce( - (bits, next) => - (bits |= typeof next === "string" ? DiscordGatewayIntents[next] : next), - 0 + ( + bits, + next, + ) => (bits |= typeof next === "string" + ? DiscordGatewayIntents[next] + : next), + 0, ); const data = (await fetch(`https://discord.com/api/gateway/bot`, { diff --git a/src/ws/tell_cluster_to_identify.ts b/src/ws/tell_cluster_to_identify.ts index 7ef75968a..2e211b959 100644 --- a/src/ws/tell_cluster_to_identify.ts +++ b/src/ws/tell_cluster_to_identify.ts @@ -4,7 +4,7 @@ import { ws } from "./ws.ts"; export async function tellClusterToIdentify( workerID: number, shardID: number, - bucketID: number + bucketID: number, ) { // When resharding this may exist already const oldShard = ws.shards.get(shardID); diff --git a/src/ws/ws.ts b/src/ws/ws.ts index 728855e14..76ae2307a 100644 --- a/src/ws/ws.ts +++ b/src/ws/ws.ts @@ -1,19 +1,15 @@ -import { Collection } from "../../util/collection.ts"; -import { - cleanupLoadingShards, - spawnShards, - startGateway, - tellClusterToIdentify, -} from "./manager.ts"; -import { - createShard, - handleDiscordPayload, - handleOnMessage, - heartbeat, - identify, -} from "./shard.ts"; +import { Collection } from "../util/collection.ts"; import { log } from "./events.ts"; import { resharder } from "./resharder.ts"; +import { startGateway } from "./start_gateway.ts"; +import { spawnShards } from "./spawn_shards.ts"; +import { createShard } from "./create_shard.ts"; +import { identify } from "./identify.ts"; +import { heartbeat } from "./heartbeat.ts"; +import { handleDiscordPayload } from "./handle_discord_payload.ts"; +import { tellClusterToIdentify } from "./tell_cluster_to_identify.ts"; +import { cleanupLoadingShards } from "./cleanup_loading_shards.ts"; +import { handleOnMessage } from "./handle_on_message.ts"; // CONTROLLER LIKE INTERFACE FOR WS HANDLING export const ws = {