From 71175dd9e169bc2ef8d7c478319b7fc0e63d59cd Mon Sep 17 00:00:00 2001 From: Donovan Daniels Date: Mon, 15 Jul 2024 06:47:51 -0500 Subject: [PATCH 1/5] Refactor handling of dispatch events --- esm.mjs | 4 + lib/gateway/Dispatcher.ts | 51 ++ lib/gateway/Shard.ts | 1160 +++-------------------------------- lib/gateway/ShardManager.ts | 78 ++- lib/gateway/events.ts | 956 +++++++++++++++++++++++++++++ lib/index.ts | 3 + lib/types/gateway.d.ts | 5 + 7 files changed, 1140 insertions(+), 1117 deletions(-) create mode 100644 lib/gateway/Dispatcher.ts create mode 100644 lib/gateway/events.ts diff --git a/esm.mjs b/esm.mjs index 4cbbf40b..3ad75e1f 100644 --- a/esm.mjs +++ b/esm.mjs @@ -22,6 +22,8 @@ const Collection = (await import("./dist/lib/util/Collection.js")).default.defau const ComponentInteraction = (await import("./dist/lib/structures/ComponentInteraction.js")).default.default; const DiscordHTTPError = (await import("./dist/lib/rest/DiscordHTTPError.js")).default.default; const DiscordRESTError = (await import("./dist/lib/rest/DiscordRESTError.js")).default.default; +const Dispatcher = (await import("./dist/lib/gateway/Dispatcher.js")).default.default; +const DefaultDispatchEvents = (await import("./dist/lib/gateway/events.js")).default; const Errors = (await import("./dist/lib/util/Errors.js")).default; const Entitlement = (await import("./dist/lib/structures/Entitlement.js")).default.default; const ExtendedUser = (await import("./dist/lib/structures/ExtendedUser.js")).default.default; @@ -109,6 +111,8 @@ export { ComponentInteraction, DiscordHTTPError, DiscordRESTError, + Dispatcher, + DefaultDispatchEvents, Errors, Entitlement, ExtendedUser, diff --git a/lib/gateway/Dispatcher.ts b/lib/gateway/Dispatcher.ts new file mode 100644 index 00000000..f5a913dc --- /dev/null +++ b/lib/gateway/Dispatcher.ts @@ -0,0 +1,51 @@ +import type Shard from "./Shard"; +import type ShardManager from "./ShardManager"; +import * as DefaultDispatchEvents from "./events"; +import type { AnyDispatchPacket } from "../types/gateway-raw"; + +export type DispatchEvent = AnyDispatchPacket["t"]; +export type DispatchEventMap = { + [K in AnyDispatchPacket as K["t"]]: K["d"]; +}; +export type DispatchFunction = (data: DispatchEventMap[K], shard: Shard) => void; +export default class Dispatcher { + private manager!: ShardManager; + events: Map> = new Map(); + constructor(manager: ShardManager) { + Object.defineProperty(this, "manager", { + value: manager, + writable: false, + enumerable: false, + configurable: false + }); + + if (this.manager.options.useDefaultDispatchHandlers) { + for (const [event, fn] of Object.entries(DefaultDispatchEvents)) { + this.register(event as DispatchEvent, fn as DispatchFunction); + } + } + } + + private handle(data: AnyDispatchPacket, shard: Shard): void { + const event = data.t; + if (!this.events.has(event)) return; + const arr = this.events.get(event)!; + for (const fn of arr) fn(data.d, shard); + } + + register(event: K, fn: DispatchFunction, replace = false): void { + if (!this.events.has(event)) this.events.set(event, []); + const arr = this.events.get(event)!; + if (replace && arr.length !== 0) arr.splice(0, arr.length); + arr.push(fn as never); + } + + unregister(event: K, fn?: DispatchFunction): void { + if (!this.events.has(event)) return; + const arr = this.events.get(event)!; + if (fn) { + const index = arr.indexOf(fn as never); + if (index !== -1) arr.splice(index, 1); + } else arr.splice(0, arr.length); + } +} diff --git a/lib/gateway/Shard.ts b/lib/gateway/Shard.ts index f27a5d7f..4f9b49ee 100644 --- a/lib/gateway/Shard.ts +++ b/lib/gateway/Shard.ts @@ -1,61 +1,25 @@ /** @module Shard */ +import type ShardManager from "./ShardManager"; import type Client from "../Client"; import TypedEmitter from "../util/TypedEmitter"; import Bucket from "../rest/Bucket"; -import { - ChannelTypes, - GatewayCloseCodes, - GatewayOPCodes, - GATEWAY_VERSION, - Intents -} from "../Constants"; +import { GatewayCloseCodes, GatewayOPCodes, GATEWAY_VERSION, Intents } from "../Constants"; import type { UpdatePresenceOptions, RequestGuildMembersOptions, UpdateVoiceStateOptions, - PresenceUpdate, SendStatuses, BotActivity, ShardStatus } from "../types/gateway"; -import Member from "../structures/Member"; +import type Member from "../structures/Member"; import Base from "../structures/Base"; -import type { AnyDispatchPacket, AnyReceivePacket } from "../types/gateway-raw"; +import type { AnyReceivePacket, ReadyPacket } from "../types/gateway-raw"; import type { RawOAuthUser, RawUser } from "../types/users"; import type { RawGuild } from "../types/guilds"; import ExtendedUser from "../structures/ExtendedUser"; -import AutoModerationRule from "../structures/AutoModerationRule"; -import Channel from "../structures/Channel"; -import type { - AnyGuildChannelWithoutThreads, - AnyTextableChannel, - AnyThreadChannel, - AnyInviteChannel, - PossiblyUncachedInvite, - RawMessage, - ThreadMember, - ThreadParentChannel, - UncachedThreadMember, - AnyVoiceChannel, - PollAnswer, - AnyGuildChannel -} from "../types/channels"; -import type { JSONAnnouncementThreadChannel } from "../types/json"; -import VoiceChannel from "../structures/VoiceChannel"; -import StageChannel from "../structures/StageChannel"; -import GuildScheduledEvent from "../structures/GuildScheduledEvent"; -import Invite from "../structures/Invite"; -import Message from "../structures/Message"; -import StageInstance from "../structures/StageInstance"; -import type AnnouncementThreadChannel from "../structures/AnnouncementThreadChannel"; -import Interaction from "../structures/Interaction"; -import Guild from "../structures/Guild"; +import type Guild from "../structures/Guild"; import type { ShardEvents } from "../types/events"; -import Role from "../structures/Role"; -import Integration from "../structures/Integration"; -import VoiceState from "../structures/VoiceState"; -import AuditLogEntry from "../structures/AuditLogEntry"; -import type User from "../structures/User"; import GatewayError, { DependencyError } from "../util/Errors"; import ClientApplication from "../structures/ClientApplication"; import WebSocket, { type Data } from "ws"; @@ -105,6 +69,7 @@ export default class Shard extends TypedEmitter { lastHeartbeatReceived: number; lastHeartbeatSent: number; latency: number; + manager!: ShardManager; preReady: boolean; presence!: Required; presenceUpdateBucket!: Bucket; @@ -115,15 +80,21 @@ export default class Shard extends TypedEmitter { sessionID: string | null; status: ShardStatus; ws!: WebSocket | null; - constructor(id: number, client: Client) { + constructor(id: number, manager: ShardManager) { super(); Object.defineProperties(this, { client: { - value: client, + value: manager.client, enumerable: false, writable: false, configurable: false }, + manager: { + value: manager, + enumerable: false, + writable: true, + configurable: false + }, ws: { value: null, enumerable: false, @@ -132,7 +103,6 @@ export default class Shard extends TypedEmitter { } }); - this.onDispatch = this.onDispatch.bind(this); this.onPacket = this.onPacket.bind(this); this.onWSClose = this.onWSClose.bind(this); this.onWSError = this.onWSError.bind(this); @@ -161,6 +131,64 @@ export default class Shard extends TypedEmitter { this.hardReset(); } + private _ready(data: ReadyPacket["d"]): void { + + this.connectAttempts = 0; + this.reconnectInterval = 1000; + this.connecting = false; + if (this._connectTimeout) { + clearInterval(this._connectTimeout); + } + this.status = "ready"; + this.client.shards["_ready"](this.id); + this.client["_application"] = new ClientApplication(data.application, this.client); + if (this.client["_user"]) { + this.client.users.update(data.user as unknown as RawUser); + } else { + this.client["_user"] = this.client.users.add(new ExtendedUser(data.user as RawOAuthUser, this.client)); + } + + let url = data.resume_gateway_url; + if (url.includes("?")) { + url = url.slice(0, url.indexOf("?")); + } + if (!url.endsWith("/")) { + url += "/"; + } + this.resumeURL = `${url}?v=${GATEWAY_VERSION}&encoding=${Erlpack ? "etf" : "json"}`; + if (this.client.shards.options.compress) { + this.resumeURL += "&compress=zlib-stream"; + } + this.sessionID = data.session_id; + + for (const guild of data.guilds) { + this.client.guilds.delete(guild.id); + this.client.unavailableGuilds.update(guild); + } + + this.preReady = true; + this.emit("preReady"); + + if (this.client.unavailableGuilds.size !== 0 && data.guilds.length !== 0) { + void this.restartGuildCreateTimeout(); + } else { + void this.checkReady(); + } + } + + private _resume(): void { + this.connectAttempts = 0; + this.reconnectInterval = 1000; + this.connecting = false; + if (this._connectTimeout) { + clearInterval(this._connectTimeout); + } + this.status = "ready"; + this.client.shards["_ready"](this.id); + void this.checkReady(); + this.emit("resume"); + } + private async checkReady(): Promise { if (!this.ready) { if (this._getAllUsersQueue.length !== 0) { @@ -227,1047 +255,6 @@ export default class Shard extends TypedEmitter { }, this.client.shards.options.connectionTimeout); } - private async onDispatch(packet: AnyDispatchPacket): Promise { - this.client.emit("packet", packet, this.id); - switch (packet.t) { - case "APPLICATION_COMMAND_PERMISSIONS_UPDATE": { - this.client.emit("applicationCommandPermissionsUpdate", this.client.guilds.get(packet.d.guild_id) ?? { id: packet.d.guild_id }, { - application: packet.d.application_id === this.client.application.id ? this.client.application : undefined, - applicationID: packet.d.application_id, - id: packet.d.id, - permissions: packet.d.permissions - }); - break; - } - - case "AUTO_MODERATION_ACTION_EXECUTION": { - const guild = this.client.guilds.get(packet.d.guild_id); - const channel = this.client.getChannel(packet.d.channel_id ?? ""); - this.client.emit( - "autoModerationActionExecution", - guild ?? { id: packet.d.guild_id }, - packet.d.channel_id === undefined ? null : channel ?? { id: packet.d.channel_id }, - this.client.users.get(packet.d.user_id) ?? { id: packet.d.user_id }, - { - action: { - metadata: { - channelID: packet.d.action.metadata.channel_id, - customMessage: packet.d.action.metadata.custom_message, - durationSeconds: packet.d.action.metadata.duration_seconds - }, - type: packet.d.action.type - }, - alertSystemMessageID: packet.d.alert_system_message_id, - content: packet.d.content, - matchedContent: packet.d.matched_content, - matchedKeyword: packet.d.matched_keyword, - messageID: packet.d.message_id, - rule: guild?.autoModerationRules.get(packet.d.rule_id), - ruleID: packet.d.rule_id, - ruleTriggerType: packet.d.rule_trigger_type - } - ); - break; - } - - case "AUTO_MODERATION_RULE_CREATE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const rule = guild?.autoModerationRules.update(packet.d) ?? new AutoModerationRule(packet.d, this.client); - this.client.emit("autoModerationRuleCreate", rule); - break; - } - - case "AUTO_MODERATION_RULE_DELETE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const rule = guild?.autoModerationRules.update(packet.d) ?? new AutoModerationRule(packet.d, this.client); - guild?.autoModerationRules.delete(packet.d.id); - this.client.emit("autoModerationRuleDelete", rule); - break; - } - - case "AUTO_MODERATION_RULE_UPDATE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const oldRule = guild?.autoModerationRules.get(packet.d.id)?.toJSON() ?? null; - const rule = guild?.autoModerationRules.update(packet.d) ?? new AutoModerationRule(packet.d, this.client); - this.client.emit("autoModerationRuleUpdate", rule, oldRule); - break; - } - - case "CHANNEL_CREATE": { - const channel = packet.d.type === ChannelTypes.GROUP_DM ? this.client.groupChannels.update(packet.d) : this.client.util.updateChannel(packet.d); - this.client.emit("channelCreate", channel); - break; - } - - case "CHANNEL_DELETE": { - if (packet.d.type === ChannelTypes.DM) { - const channel = this.client.privateChannels.get(packet.d.id); - this.client.privateChannels.delete(packet.d.id); - this.client.emit("channelDelete", channel ?? { - id: packet.d.id, - flags: packet.d.flags, - lastMessageID: packet.d.last_message_id, - type: packet.d.type - }); - break; - } - const guild = this.client.guilds.get(packet.d.guild_id); - const channel = this.client.util.updateChannel(packet.d); - if (channel instanceof VoiceChannel || channel instanceof StageChannel) { - for (const [,member] of channel.voiceMembers) { - channel.voiceMembers.delete(member.id); - this.client.emit("voiceChannelLeave", member, channel); - } - } - guild?.channels.delete(packet.d.id); - this.client.emit("channelDelete", channel); - break; - } - - case "CHANNEL_PINS_UPDATE": { - const channel = this.client.getChannel(packet.d.channel_id); - this.client.emit("channelPinsUpdate", channel ?? { id: packet.d.channel_id }, packet.d.last_pin_timestamp === undefined || packet.d.last_pin_timestamp === null ? null : new Date(packet.d.last_pin_timestamp)); - break; - } - - case "CHANNEL_UPDATE": { - const oldChannel = this.client.getChannel(packet.d.id)?.toJSON() ?? null; - let channel: AnyGuildChannel; - if (oldChannel && oldChannel.type !== packet.d.type) { - if (this.client.channelGuildMap[packet.d.id]) { - this.client.guilds.get(this.client.channelGuildMap[packet.d.id])!.channels.delete(packet.d.id); - } - - channel = this.client.util.updateChannel(packet.d); - } else { - channel = this.client.util.updateChannel(packet.d); - } - this.client.emit("channelUpdate", channel, oldChannel); - break; - } - - case "ENTITLEMENT_CREATE": { - const entitlement = this.client.util.updateEntitlement(packet.d); - this.client.emit("entitlementCreate", entitlement); - break; - } - - case "ENTITLEMENT_DELETE": { - const entitlement = this.client.util.updateEntitlement(packet.d); - this.client["_application"]?.entitlements.delete(packet.d.id); - this.client.emit("entitlementDelete", entitlement); - break; - } - - case "ENTITLEMENT_UPDATE": { - const oldEntitlement = this.client["_application"]?.entitlements.get(packet.d.id)?.toJSON() ?? null; - const entitlement = this.client.util.updateEntitlement(packet.d); - this.client.emit("entitlementUpdate", entitlement, oldEntitlement); - break; - } - - case "GUILD_AUDIT_LOG_ENTRY_CREATE": { - const guild = this.client.guilds.get(packet.d.guild_id); - this.client.emit("guildAuditLogEntryCreate", guild ?? { id: packet.d.guild_id }, guild?.auditLogEntries.update(packet.d) ?? new AuditLogEntry(packet.d, this.client)); - break; - } - - case "GUILD_BAN_ADD": { - this.client.emit("guildBanAdd", this.client.guilds.get(packet.d.guild_id) ?? { id: packet.d.guild_id }, this.client.users.update(packet.d.user)); - break; - } - - case "GUILD_BAN_REMOVE": { - this.client.emit("guildBanRemove", this.client.guilds.get(packet.d.guild_id) ?? { id: packet.d.guild_id }, this.client.users.update(packet.d.user)); - break; - } - - case "GUILD_CREATE": { - if (packet.d.unavailable) { - this.client.guilds.delete(packet.d.id); - this.client.emit("unavailableGuildCreate", this.client.unavailableGuilds.update(packet.d)); - } else { - const guild = this.createGuild(packet.d); - if (this.ready) { - if (this.client.unavailableGuilds.delete(guild.id)) { - this.client.emit("guildAvailable", guild); - } else { - this.client.emit("guildCreate", guild); - } - } else { - if (this.client.unavailableGuilds.delete(guild.id)) { - void this.restartGuildCreateTimeout(); - } else { - this.client.emit("guildCreate", guild); - } - } - } - break; - } - - case "GUILD_DELETE": { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - this.client.voiceAdapters.get(packet.d.id)?.destroy(); - delete this.client.guildShardMap[packet.d.id]; - const guild = this.client.guilds.get(packet.d.id); - guild?.channels.clear(); - guild?.threads.clear(); - this.client.guilds.delete(packet.d.id); - if (packet.d.unavailable) { - this.client.emit("guildUnavailable", this.client.unavailableGuilds.update(packet.d)); - } else { - this.client.emit("guildDelete", guild ?? { id: packet.d.id }); - } - break; - } - - case "GUILD_EMOJIS_UPDATE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const oldEmojis = guild?.emojis ? guild.emojis.toArray() : null; - // eslint-disable-next-line @typescript-eslint/dot-notation - guild?.["update"]({ emojis: packet.d.emojis }); - this.client.emit( - "guildEmojisUpdate", - guild ?? { id: packet.d.guild_id }, - guild?.emojis?.toArray() ?? packet.d.emojis.map(emoji => this.client.util.convertEmoji(emoji)), - oldEmojis - ); - break; - } - - case "GUILD_INTEGRATIONS_UPDATE": { - this.client.emit("guildIntegrationsUpdate", this.client.guilds.get(packet.d.guild_id) ?? { id: packet.d.guild_id }); - break; - } - - case "GUILD_MEMBER_ADD": { - const guild = this.client.guilds.get(packet.d.guild_id); - if (guild) { - guild.memberCount++; - } - const member = this.client.util.updateMember(packet.d.guild_id, packet.d.user!.id, packet.d); - this.client.emit("guildMemberAdd", member); - break; - } - - case "GUILD_MEMBERS_CHUNK": { - const guild = this.client.guilds.get(packet.d.guild_id); - // eslint-disable-next-line @typescript-eslint/dot-notation - guild?.["updateMemberLimit"](packet.d.members.length); - const members = packet.d.members.map(member => this.client.util.updateMember(packet.d.guild_id, member.user!.id, member)); - if (packet.d.presences) for (const presence of packet.d.presences) { - const member = members.find(m => m.id === presence.user.id)!; - member.presence = { - clientStatus: presence.client_status, - guildID: presence.guild_id, - status: presence.status, - activities: presence.activities?.map(activity => ({ - createdAt: activity.created_at, - name: activity.name, - type: activity.type, - applicationID: activity.application_id, - assets: activity.assets ? { - largeImage: activity.assets.large_image, - largeText: activity.assets.large_text, - smallImage: activity.assets.small_image, - smallText: activity.assets.small_text - } : undefined, - buttons: activity.buttons, - details: activity.details, - emoji: activity.emoji, - flags: activity.flags, - instance: activity.instance, - party: activity.party, - secrets: activity.secrets, - state: activity.state, - timestamps: activity.timestamps, - url: activity.url - })) - }; - } - if (!packet.d.nonce) { - this.client.emit("warn", "Received GUILD_MEMBERS_CHUNK without a nonce."); - break; - } - if (this._requestMembersPromise[packet.d.nonce]) { - this._requestMembersPromise[packet.d.nonce].members.push(...members); - } - - if (packet.d.chunk_index >= packet.d.chunk_count - 1) { - if (this._requestMembersPromise[packet.d.nonce]) { - clearTimeout(this._requestMembersPromise[packet.d.nonce].timeout); - this._requestMembersPromise[packet.d.nonce].resolve(this._requestMembersPromise[packet.d.nonce].members); - delete this._requestMembersPromise[packet.d.nonce]; - } - if (this._getAllUsersCount[packet.d.guild_id]) { - delete this._getAllUsersCount[packet.d.guild_id]; - void this.checkReady(); - } - } - - this.client.emit("guildMemberChunk", members); - this.lastHeartbeatAck = true; - break; - } - - case "GUILD_MEMBER_REMOVE": { - if (packet.d.user.id === this.client.user.id) { - break; - } - const guild = this.client.guilds.get(packet.d.guild_id); - // eslint-disable-next-line @typescript-eslint/dot-notation - let user: Member | User | undefined = guild?.members.get(packet.d.user.id); - if (user instanceof Member) { - user["update"]({ user: packet.d.user }); - } else { - user = this.client.users.update(packet.d.user); - } - if (guild) { - guild.memberCount--; - guild.members.delete(packet.d.user.id); - } - this.client.emit("guildMemberRemove", user, guild ?? { id: packet.d.guild_id }); - break; - } - - case "GUILD_MEMBER_UPDATE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const oldMember = guild?.members.get(packet.d.user.id)?.toJSON() ?? null; - const member = this.client.util.updateMember(packet.d.guild_id, packet.d.user.id, { deaf: oldMember?.deaf ?? false, mute: oldMember?.mute ?? false, ...packet.d }); - this.client.emit("guildMemberUpdate", member, oldMember); - break; - } - - case "GUILD_ROLE_CREATE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const role = guild?.roles.update(packet.d.role, packet.d.guild_id) ?? new Role(packet.d.role, this.client, packet.d.guild_id); - this.client.emit("guildRoleCreate", role); - break; - } - - case "GUILD_ROLE_DELETE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const role = guild?.roles.get(packet.d.role_id); - guild?.roles.delete(packet.d.role_id); - this.client.emit("guildRoleDelete", role ?? { id: packet.d.role_id }, guild ?? { id: packet.d.guild_id }); - break; - } - - case "GUILD_ROLE_UPDATE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const oldRole = guild?.roles.get(packet.d.role.id)?.toJSON() ?? null; - const role = guild?.roles.update(packet.d.role, packet.d.guild_id) ?? new Role(packet.d.role, this.client, packet.d.guild_id); - this.client.emit("guildRoleUpdate", role, oldRole); - break; - } - - case "GUILD_SCHEDULED_EVENT_CREATE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const event = guild?.scheduledEvents.update(packet.d) ?? new GuildScheduledEvent(packet.d, this.client); - this.client.emit("guildScheduledEventCreate", event); - break; - } - - case "GUILD_SCHEDULED_EVENT_DELETE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const event = guild?.scheduledEvents.update(packet.d) ?? new GuildScheduledEvent(packet.d, this.client); - guild?.scheduledEvents.delete(packet.d.id); - this.client.emit("guildScheduledEventDelete", event); - break; - } - - case "GUILD_SCHEDULED_EVENT_UPDATE": { - const guild = this.client.guilds.get(packet.d.guild_id)!; - const oldEvent = guild?.scheduledEvents.get(packet.d.id)?.toJSON() ?? null; - const event = guild?.scheduledEvents.update(packet.d) ?? new GuildScheduledEvent(packet.d, this.client); - this.client.emit("guildScheduledEventUpdate", event, oldEvent); - break; - } - - case "GUILD_SCHEDULED_EVENT_USER_ADD": { - const guild = this.client.guilds.get(packet.d.guild_id); - const event = guild?.scheduledEvents.get(packet.d.guild_scheduled_event_id); - if (event?.userCount) { - event.userCount++; - } - const user = this.client.users.get(packet.d.user_id) ?? { id: packet.d.user_id }; - this.client.emit("guildScheduledEventUserAdd", event ?? { id: packet.d.guild_scheduled_event_id }, user ?? { id: packet.d.user_id }); - break; - } - - case "GUILD_SCHEDULED_EVENT_USER_REMOVE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const event = guild?.scheduledEvents.get(packet.d.guild_scheduled_event_id); - if (event?.userCount) { - event.userCount--; - } - const user = this.client.users.get(packet.d.user_id) ?? { id: packet.d.user_id }; - this.client.emit("guildScheduledEventUserRemove", event ?? { id: packet.d.guild_scheduled_event_id }, user ?? { id: packet.d.user_id }); - break; - } - - case "GUILD_STICKERS_UPDATE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const oldStickers = guild?.stickers ? guild.stickers.toArray() : null; - // eslint-disable-next-line @typescript-eslint/dot-notation - guild?.["update"]({ stickers: packet.d.stickers }); - this.client.emit("guildStickersUpdate", guild ?? { id: packet.d.guild_id }, guild?.stickers?.toArray() ?? packet.d.stickers.map(sticker => this.client.util.convertSticker(sticker)), oldStickers); - break; - } - - case "GUILD_UPDATE": { - const guild = this.client.guilds.get(packet.d.id); - const oldGuild = guild?.toJSON() ?? null; - this.client.emit("guildUpdate", this.client.guilds.update(packet.d), oldGuild); - break; - } - - case "INTEGRATION_CREATE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const integration = guild?.integrations.update(packet.d, packet.d.guild_id) ?? new Integration(packet.d, this.client, packet.d.guild_id); - this.client.emit("integrationCreate", guild ?? { id: packet.d.guild_id }, integration); - break; - } - - case "INTEGRATION_DELETE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const integration = guild?.integrations.get(packet.d.id); - guild?.integrations.delete(packet.d.id); - this.client.emit("integrationDelete", guild ?? { id: packet.d.guild_id }, integration ?? { applicationID: packet.d.application_id, id: packet.d.id }); - break; - } - - case "INTEGRATION_UPDATE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const oldIntegration = guild?.integrations.get(packet.d.id)?.toJSON() ?? null; - const integration = guild?.integrations.update(packet.d, packet.d.guild_id) ?? new Integration(packet.d, this.client, packet.d.guild_id); - this.client.emit("integrationUpdate", guild ?? { id: packet.d.guild_id }, integration, oldIntegration); - break; - } - - case "INTERACTION_CREATE": { - this.client.emit("interactionCreate", Interaction.from(packet.d, this.client)); - break; - } - - case "INVITE_CREATE": { - let invite: Invite | undefined; - if (packet.d.guild_id) { - const guild = this.client.guilds.get(packet.d.guild_id); - invite = guild?.invites.update(packet.d); - } - this.client.emit("inviteCreate", invite ?? new Invite(packet.d, this.client)); - break; - } - - case "INVITE_DELETE": { - const channel = this.client.getChannel(packet.d.channel_id) ?? { id: packet.d.channel_id }; - const guild = packet.d.guild_id ? this.client.guilds.get(packet.d.guild_id) ?? { id: packet.d.guild_id } : undefined; - let invite: PossiblyUncachedInvite = { - code: packet.d.code, - channel, - guild - }; - if (guild instanceof Guild && guild.invites.has(packet.d.code)) { - invite = guild.invites.get(packet.d.code)!; - guild.invites.delete(packet.d.code); - } - this.client.emit("inviteDelete", invite); - break; - } - - case "MESSAGE_CREATE": { - const channel = this.client.getChannel(packet.d.channel_id); - const message = channel?.messages?.update(packet.d) ?? new Message(packet.d, this.client); - if (channel) { - channel.lastMessage = message as never; - channel.lastMessageID = message.id; - } - this.client.emit("messageCreate", message); - break; - } - - case "MESSAGE_DELETE": { - const channel = this.client.getChannel(packet.d.channel_id); - const message = channel?.messages?.get(packet.d.id); - if (channel) { - channel.messages?.delete(packet.d.id); - if (channel.lastMessageID === packet.d.id) { - channel.lastMessageID = null; - channel.lastMessage = null; - } - } - this.client.emit("messageDelete", message ?? { - channel: channel ?? { id: packet.d.channel_id }, - channelID: packet.d.channel_id, - guild: packet.d.guild_id ? this.client.guilds.get(packet.d.guild_id) : undefined, - guildID: packet.d.guild_id, id: packet.d.id - }); - break; - } - - case "MESSAGE_DELETE_BULK": { - const channel = this.client.getChannel(packet.d.channel_id); - const guild = packet.d.guild_id ? this.client.guilds.get(packet.d.guild_id) : undefined; - this.client.emit("messageDeleteBulk", packet.d.ids.map(id => { - const message = channel?.messages?.get(id); - channel?.messages?.delete(id); - return message ?? { - channel: channel ?? { id: packet.d.channel_id }, - channelID: packet.d.channel_id, - guild, - guildID: packet.d.guild_id, - id - }; - })); - break; - } - - case "MESSAGE_POLL_VOTE_ADD": { - const user = this.client.users.get(packet.d.user_id) ?? { id: packet.d.user_id }; - const channel = this.client.getChannel(packet.d.channel_id) ?? { id: packet.d.channel_id }; - const guild = packet.d.guild_id ? this.client.guilds.get(packet.d.guild_id) : undefined; - const message = (channel instanceof Channel ? channel.messages.get(packet.d.message_id) : undefined) ?? { channel, channelID: channel.id, guild, guildID: guild?.id, id: packet.d.message_id }; - let answer: PollAnswer | { answerID: number; } = { answerID: packet.d.answer_id }; - if (message instanceof Message && message.poll !== undefined) { - const pollAnswer = message.poll.answers.find(a => a.answerID === packet.d.answer_id); - if (pollAnswer) { - answer = pollAnswer; - } - - this.client.util.updatePollAnswer(message.poll, packet.d.answer_id, 1, packet.d.user_id); - } - this.client.emit("messagePollVoteAdd", message, user, answer); - break; - } - - case "MESSAGE_POLL_VOTE_REMOVE": { - const user = this.client.users.get(packet.d.user_id) ?? { id: packet.d.user_id }; - const channel = this.client.getChannel(packet.d.channel_id) ?? { id: packet.d.channel_id }; - const guild = packet.d.guild_id ? this.client.guilds.get(packet.d.guild_id) : undefined; - const message = (channel instanceof Channel ? channel.messages.get(packet.d.message_id) : undefined) ?? { channel, channelID: channel.id, guild, guildID: guild?.id, id: packet.d.message_id }; - let answer: PollAnswer | { answerID: number; } = { answerID: packet.d.answer_id }; - if (message instanceof Message && message.poll !== undefined) { - const pollAnswer = message.poll.answers.find(a => a.answerID === packet.d.answer_id); - if (pollAnswer) { - answer = pollAnswer; - } - - this.client.util.updatePollAnswer(message.poll, packet.d.answer_id, -1, packet.d.user_id); - } - this.client.emit("messagePollVoteRemove", message, user, answer); - break; - } - - case "MESSAGE_REACTION_ADD": { - const channel = this.client.getChannel(packet.d.channel_id); - const guild = packet.d.guild_id ? this.client.guilds.get(packet.d.guild_id) : undefined; - const message = channel?.messages?.get(packet.d.message_id); - const reactor = packet.d.member - ? (packet.d.guild_id ? this.client.util.updateMember(packet.d.guild_id, packet.d.user_id, packet.d.member) : this.client.users.get(packet.d.user_id) ?? { id: packet.d.user_id }) - : this.client.users.get(packet.d.user_id) ?? { id: packet.d.user_id }; - - if (message) { - const index = message.reactions.findIndex(r => r.emoji.id === packet.d.emoji.id && r.emoji.name === packet.d.emoji.name); - if (index === -1) { - message.reactions.push({ - burstColors: packet.d.burst_colors, - count: 1, - countDetails: { - burst: packet.d.burst ? 1 : 0, - normal: packet.d.burst ? 0 : 1 - }, - emoji: packet.d.emoji, - me: packet.d.user_id === this.client.user.id, - meBurst: packet.d.user_id === this.client.user.id && packet.d.burst - }); - } else { - if (packet.d.burst) { - message.reactions[index].countDetails.burst++; - } else { - message.reactions[index].countDetails.normal++; - } - message.reactions[index].count++; - if (packet.d.user_id === this.client.user.id) { - message.reactions[index].me = true; - } - } - - } - - this.client.emit("messageReactionAdd", message ?? { - channel: channel ?? { id: packet.d.channel_id }, - channelID: packet.d.channel_id, - guild, - guildID: packet.d.guild_id, - id: packet.d.message_id , - author: packet.d.message_author_id === undefined ? undefined : this.client.users.get(packet.d.message_author_id) ?? { id: packet.d.message_author_id }, - member: packet.d.message_author_id === undefined ? undefined : guild?.members.get(packet.d.message_author_id) ?? { id: packet.d.message_author_id } - }, reactor, { - burst: packet.d.burst, - burstColors: packet.d.burst_colors, - emoji: packet.d.emoji, - type: packet.d.type - }); - break; - } - - case "MESSAGE_REACTION_REMOVE": { - const channel = this.client.getChannel(packet.d.channel_id); - const message = channel?.messages?.get(packet.d.message_id); - const reactor = this.client.users.get(packet.d.user_id) ?? { id: packet.d.user_id }; - - if (message) { - const index = message.reactions.findIndex(r => r.emoji.id === packet.d.emoji.id && r.emoji.name === packet.d.emoji.name); - if (index !== -1) { - if (packet.d.burst) { - message.reactions[index].countDetails.burst--; - } else { - message.reactions[index].countDetails.normal--; - } - message.reactions[index].count--; - if (packet.d.user_id === this.client.user.id) { - if (packet.d.burst) { - message.reactions[index].meBurst = false; - } else { - message.reactions[index].me = false; - } - } - if (message.reactions[index].count === 0) { - message.reactions.splice(index, 1); - } - } - } - - this.client.emit("messageReactionRemove", message ?? { - channel: channel ?? { id: packet.d.channel_id }, - channelID: packet.d.channel_id, - guild: packet.d.guild_id ? this.client.guilds.get(packet.d.guild_id) : undefined, - guildID: packet.d.guild_id, - id: packet.d.message_id - }, reactor, { - burst: packet.d.burst, - burstColors: packet.d.burst_colors, - emoji: packet.d.emoji, - type: packet.d.type - }); - break; - } - - case "MESSAGE_REACTION_REMOVE_ALL": { - const channel = this.client.getChannel(packet.d.channel_id); - const message = channel?.messages?.get(packet.d.message_id); - - if (message) { - message.reactions = []; - } - - this.client.emit("messageReactionRemoveAll", message ?? { - channel: channel ?? { id: packet.d.channel_id }, - channelID: packet.d.channel_id, - guild: packet.d.guild_id ? this.client.guilds.get(packet.d.guild_id) : undefined, - guildID: packet.d.guild_id, - id: packet.d.message_id - }); - break; - } - - case "MESSAGE_REACTION_REMOVE_EMOJI": { - const channel = this.client.getChannel(packet.d.channel_id); - const message = channel?.messages?.get(packet.d.message_id); - - if (message) { - const index = message.reactions.findIndex(r => r.emoji.id === packet.d.emoji.id && r.emoji.name === packet.d.emoji.name); - if (index !== -1) { - message.reactions.splice(index, 1); - } - } - - this.client.emit("messageReactionRemoveEmoji", message ?? { - channel: channel ?? { id: packet.d.channel_id }, - channelID: packet.d.channel_id, - guild: packet.d.guild_id ? this.client.guilds.get(packet.d.guild_id) : undefined, - guildID: packet.d.guild_id, - id: packet.d.message_id - }, packet.d.emoji); - break; - } - - case "MESSAGE_UPDATE": { - const channel = this.client.getChannel(packet.d.channel_id); - const oldMessage = channel?.messages?.get(packet.d.id)?.toJSON() ?? null; - if (!oldMessage && !packet.d.author) { - this.client.emit("debug", `Got partial MESSAGE_UPDATE for uncached message ${packet.d.id} for channel ${packet.d.channel_id}, discarding..`); - break; - } - const message = channel?.messages?.update(packet.d) ?? new Message(packet.d as RawMessage, this.client); - this.client.emit("messageUpdate", message, oldMessage); - break; - } - - case "PRESENCE_UPDATE": { - const user = this.client.users.get(packet.d.user.id); - if (user) { - const oldUser = user.toJSON(); - user["update"](packet.d.user); - if (JSON.stringify(oldUser) !== JSON.stringify(user.toJSON())) { - this.client.emit("userUpdate", user, oldUser); - } - } - - const guild = this.client.guilds.get(packet.d.guild_id); - const member = guild?.members.get(packet.d.user.id); - const oldPresence = member?.presence ?? null; - - const presence = { - clientStatus: packet.d.client_status, - guildID: packet.d.guild_id, - status: packet.d.status, - activities: packet.d.activities?.map(activity => ({ - createdAt: activity.created_at, - name: activity.name, - type: activity.type, - applicationID: activity.application_id, - assets: activity.assets ? { - largeImage: activity.assets.large_image, - largeText: activity.assets.large_text, - smallImage: activity.assets.small_image, - smallText: activity.assets.small_text - } : undefined, - buttons: activity.buttons, - details: activity.details, - emoji: activity.emoji, - flags: activity.flags, - instance: activity.instance, - party: activity.party, - secrets: activity.secrets, - state: activity.state, - timestamps: activity.timestamps, - url: activity.url - })) - }; - const userID = packet.d.user.id; - - delete (packet.d as { user?: PresenceUpdate["user"]; }).user; - if (member) { - member.presence = presence; - } - - this.client.emit("presenceUpdate", guild ?? { id: packet.d.guild_id }, member ?? { id: userID }, presence, oldPresence); - break; - } - - case "READY": { - this.connectAttempts = 0; - this.reconnectInterval = 1000; - this.connecting = false; - if (this._connectTimeout) { - clearInterval(this._connectTimeout); - } - this.status = "ready"; - this.client.shards["_ready"](this.id); - this.client["_application"] = new ClientApplication(packet.d.application, this.client); - if (this.client["_user"]) { - this.client.users.update(packet.d.user as unknown as RawUser); - } else { - this.client["_user"] = this.client.users.add(new ExtendedUser(packet.d.user as RawOAuthUser, this.client)); - } - - let url = packet.d.resume_gateway_url; - if (url.includes("?")) { - url = url.slice(0, url.indexOf("?")); - } - if (!url.endsWith("/")) { - url += "/"; - } - this.resumeURL = `${url}?v=${GATEWAY_VERSION}&encoding=${Erlpack ? "etf" : "json"}`; - if (this.client.shards.options.compress) { - this.resumeURL += "&compress=zlib-stream"; - } - this.sessionID = packet.d.session_id; - - for (const guild of packet.d.guilds) { - this.client.guilds.delete(guild.id); - this.client.unavailableGuilds.update(guild); - } - - this.preReady = true; - this.emit("preReady"); - - if (this.client.unavailableGuilds.size !== 0 && packet.d.guilds.length !== 0) { - void this.restartGuildCreateTimeout(); - } else { - void this.checkReady(); - } - break; - } - - case "RESUMED": { - this.connectAttempts = 0; - this.reconnectInterval = 1000; - this.connecting = false; - if (this._connectTimeout) { - clearInterval(this._connectTimeout); - } - this.status = "ready"; - this.client.shards["_ready"](this.id); - void this.checkReady(); - this.emit("resume"); - break; - } - - case "STAGE_INSTANCE_CREATE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const stateInstance = guild?.stageInstances.update(packet.d) ?? new StageInstance(packet.d, this.client); - this.client.emit("stageInstanceCreate", stateInstance); - break; - } - - case "STAGE_INSTANCE_DELETE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const stateInstance = guild?.stageInstances.update(packet.d) ?? new StageInstance(packet.d, this.client); - guild?.stageInstances.delete(packet.d.id); - this.client.emit("stageInstanceDelete", stateInstance); - break; - } - - case "STAGE_INSTANCE_UPDATE": { - const guild = this.client.guilds.get(packet.d.guild_id); - const oldStageInstance = guild?.stageInstances.get(packet.d.id)?.toJSON() ?? null; - const stateInstance = guild?.stageInstances.update(packet.d) ?? new StageInstance(packet.d, this.client); - this.client.emit("stageInstanceUpdate", stateInstance, oldStageInstance); - break; - } - - case "THREAD_CREATE": { - const thread = this.client.util.updateThread(packet.d); - const channel = this.client.getChannel(packet.d.parent_id!); - if (channel && channel.type === ChannelTypes.GUILD_FORUM) { - channel.lastThreadID = thread.id; - } - this.client.emit("threadCreate", thread); - break; - } - - case "THREAD_DELETE": { - const channel = this.client.getChannel(packet.d.parent_id!); - const thread = this.client.getChannel(packet.d.id) ?? { - id: packet.d.id, - guild: this.client.guilds.get(packet.d.guild_id), - guildID: packet.d.guild_id, - parent: channel || { id: packet.d.parent_id! }, - parentID: packet.d.parent_id!, - type: packet.d.type - }; - if (channel && channel.type === ChannelTypes.GUILD_FORUM && channel.lastThreadID === packet.d.id) { - channel.lastThreadID = null; - } - this.client.guilds.get(packet.d.guild_id)?.threads.delete(packet.d.id); - this.client.emit("threadDelete", thread); - break; - } - - case "THREAD_LIST_SYNC": { - const guild = this.client.guilds.get(packet.d.guild_id); - if (!guild) { - this.client.emit("debug", `Missing guild in THREAD_LIST_SYNC: ${packet.d.guild_id}`); - break; - } - for (const threadData of packet.d.threads) { - this.client.util.updateThread(threadData); - } - for (const member of packet.d.members) { - const thread = this.client.getChannel(member.id); - if (thread) { - const threadMember: ThreadMember = { - id: member.id, - flags: member.flags, - joinTimestamp: new Date(member.join_timestamp), - userID: member.user_id - }; - const index = thread.members.findIndex(m => m.userID === member.user_id); - if (index === -1) { - thread.members.push(threadMember); - } else { - thread.members[index] = threadMember; - } - } - } - break; - } - - case "THREAD_MEMBER_UPDATE": { - const thread = this.client.getChannel(packet.d.id); - const guild = this.client.guilds.get(packet.d.guild_id); - const threadMember: ThreadMember = { - id: packet.d.id, - flags: packet.d.flags, - joinTimestamp: new Date(packet.d.join_timestamp), - userID: packet.d.user_id - }; - let oldThreadMember: ThreadMember | null = null; - if (thread) { - const index = thread.members.findIndex(m => m.userID === packet.d.user_id); - if (index === -1) { - thread.members.push(threadMember); - } else { - oldThreadMember = { ...thread.members[index] }; - thread.members[index] = threadMember; - } - } - - this.client.emit( - "threadMemberUpdate", - thread ?? { - id: packet.d.id, - guild, - guildID: packet.d.guild_id - }, - threadMember, - oldThreadMember - ); - break; - } - - case "THREAD_MEMBERS_UPDATE": { - const thread = this.client.getChannel(packet.d.id); - const guild = this.client.guilds.get(packet.d.guild_id); - const addedMembers: Array = (packet.d.added_members ?? []).map(rawMember => ({ - flags: rawMember.flags, - id: rawMember.id, - joinTimestamp: new Date(rawMember.join_timestamp), - userID: rawMember.user_id - })); - const removedMembers: Array = (packet.d.removed_member_ids ?? []).map(id => ({ userID: id, id: packet.d.id })); - if (thread) { - thread.memberCount = packet.d.member_count; - for (const rawMember of addedMembers) { - const index = thread.members.findIndex(m => m.userID === rawMember.id); - if (index === -1) { - thread.members.push(rawMember); - } else { - thread.members[index] = rawMember; - } - } - for (const [index, { userID }] of removedMembers.entries()) { - const memberIndex = thread.members.findIndex(m => m.userID === userID); - if (memberIndex >= 0) { - removedMembers[index] = thread.members[memberIndex]; - thread.members.splice(memberIndex, 1); - } - } - } - this.client.emit( - "threadMembersUpdate", - thread ?? { - id: packet.d.id, - guild, - guildID: packet.d.guild_id - }, - addedMembers, - removedMembers - ); - break; - } - - case "THREAD_UPDATE": { - const oldThread = this.client.getChannel(packet.d.id)?.toJSON() ?? null; - const thread = this.client.util.updateThread(packet.d); - this.client.emit("threadUpdate", thread as AnnouncementThreadChannel, oldThread as JSONAnnouncementThreadChannel); - break; - } - - case "TYPING_START": { - const channel = this.client.getChannel(packet.d.channel_id) ?? { id: packet.d.channel_id }; - const startTimestamp = new Date(packet.d.timestamp); - if (packet.d.member) { - const member = this.client.util.updateMember(packet.d.guild_id!, packet.d.user_id, packet.d.member); - this.client.emit("typingStart", channel, member, startTimestamp); - break; - } - const user = this.client.users.get(packet.d.user_id); - this.client.emit("typingStart", channel, user ?? { id: packet.d.user_id }, startTimestamp); - break; - } - - case "USER_UPDATE": { - const oldUser = this.client.users.get(packet.d.id)?.toJSON() ?? null; - this.client.emit("userUpdate", this.client.users.update(packet.d), oldUser); - break; - } - - case "VOICE_CHANNEL_EFFECT_SEND": { - const channel = this.client.getChannel(packet.d.channel_id); - const guild = this.client.guilds.get(packet.d.guild_id); - const user = guild?.members.get(packet.d.user_id) ?? this.client.users.get(packet.d.user_id); - this.client.emit("voiceChannelEffectSend", channel ?? { id: packet.d.channel_id, guild: guild ?? { id: packet.d.guild_id } }, user ?? { id: packet.d.user_id }, { - animationID: packet.d.animation_id, - animationType: packet.d.animation_type - }); - break; - } - - case "VOICE_STATE_UPDATE": { - if (packet.d.guild_id && packet.d.session_id && packet.d.user_id === this.client.user.id) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - this.client.voiceAdapters.get(packet.d.guild_id)?.onVoiceStateUpdate(packet.d as never); - } - // @TODO voice states without guilds? - if (!packet.d.guild_id || !packet.d.member) { - break; - } - packet.d.self_stream = !!packet.d.self_stream; - const guild = this.client.guilds.get(packet.d.guild_id); - const member = this.client.util.updateMember(packet.d.guild_id, packet.d.user_id, packet.d.member); - - const oldState = guild?.voiceStates.get(member.id)?.toJSON() ?? null; - const state = guild?.voiceStates.update({ ...packet.d, id: member.id }) ?? new VoiceState(packet.d, this.client); - member["update"]({ deaf: state.deaf, mute: state.mute }); - - if (oldState?.channelID !== state.channelID) { - const oldChannel = oldState?.channelID ? this.client.getChannel(oldState.channelID) ?? { id: oldState.channelID } : null; - const newChannel = state.channel === null ? null : state.channel ?? { id: state.channelID! }; - - if (newChannel instanceof Channel) { - newChannel.voiceMembers.add(member); - } - if (oldChannel instanceof Channel) { - oldChannel.voiceMembers.delete(member.id); - } - if (oldChannel && newChannel) { - this.client.emit("voiceChannelSwitch", member, newChannel, oldChannel); - } else if (newChannel) { - this.client.emit("voiceChannelJoin", member, newChannel); - } else if (state.channelID === null) { - this.client.emit("voiceChannelLeave", member, oldChannel); - } - } - - if (JSON.stringify(oldState) !== JSON.stringify(state.toJSON())) { - this.client.emit("voiceStateUpdate", member, oldState); - } - - break; - } - - case "VOICE_CHANNEL_STATUS_UPDATE": { - this.client.emit("voiceChannelStatusUpdate", this.client.getChannel(packet.d.id) ?? { id: packet.d.id }, packet.d.status); - break; - } - - case "VOICE_SERVER_UPDATE": { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - this.client.voiceAdapters.get(packet.d.guild_id)?.onVoiceServerUpdate(packet.d); - break; - } - - case "WEBHOOKS_UPDATE": { - this.client.emit("webhooksUpdate", this.client.guilds.get(packet.d.guild_id) ?? { id: packet.d.guild_id }, this.client.getChannel(packet.d.channel_id) ?? { id: packet.d.channel_id }); - break; - } - } - } - private onPacket(packet: AnyReceivePacket): void { if ("s" in packet && packet.s) { if (packet.s > this.sequence + 1 && this.ws && this.status !== "resuming") { @@ -1279,7 +266,8 @@ export default class Shard extends TypedEmitter { switch (packet.op) { case GatewayOPCodes.DISPATCH: { - void this.onDispatch(packet); + this.client.emit("packet", packet, this.id); + this.manager.dispatcher["handle"](packet, this); break; } diff --git a/lib/gateway/ShardManager.ts b/lib/gateway/ShardManager.ts index ed665fe5..45572aba 100644 --- a/lib/gateway/ShardManager.ts +++ b/lib/gateway/ShardManager.ts @@ -1,5 +1,6 @@ /** @module ShardManager */ import Shard from "./Shard"; +import Dispatcher from "./Dispatcher"; import type Client from "../Client"; import { AllIntents, @@ -24,16 +25,16 @@ try { /** A manager for all the client's shards. */ export default class ShardManager extends Collection { private _buckets: Record; - private _client: Client; private _connectQueue: Array; private _connectTimeout: NodeJS.Timeout | null; private _gatewayURL?: string; + client!: Client; connected = false; + dispatcher!: Dispatcher; options: ShardManagerInstanceOptions; constructor(client: Client, options: GatewayOptions = {}) { super(); this._buckets = {}; - this._client = client; this._connectQueue = []; this._connectTimeout = null; this.options = { @@ -66,11 +67,12 @@ export default class ShardManager extends Collection { afk: options.presence?.afk ?? false, status: options.presence?.status ?? "online" }, - reconnectDelay: options.reconnectDelay ?? ((lastDelay, attempts): number => Math.pow(attempts + 1, 0.7) * 20000), - removeDisallowedIntents: options.removeDisallowedIntents ?? false, - seedVoiceConnections: options.seedVoiceConnections ?? false, - shardIDs: options.shardIDs ?? [], - ws: options.ws ?? {} + reconnectDelay: options.reconnectDelay ?? ((lastDelay, attempts): number => Math.pow(attempts + 1, 0.7) * 20000), + removeDisallowedIntents: options.removeDisallowedIntents ?? false, + seedVoiceConnections: options.seedVoiceConnections ?? false, + shardIDs: options.shardIDs ?? [], + useDefaultDispatchHandlers: options.useDefaultDispatchHandlers ?? true, + ws: options.ws ?? {} }; this.options.override.appendQuery ??= (this.options.override.getBot === undefined && this.options.override.url === undefined); this.options.override.gatewayURLIsResumeURL ??= (this.options.override.getBot !== undefined || this.options.override.url !== undefined); @@ -99,7 +101,7 @@ export default class ShardManager extends Collection { bitmask = AllNonPrivilegedIntents; continue; } - this._client.emit("warn", `Unknown intent: ${intent}`); + this.client.emit("warn", `Unknown intent: ${intent}`); } } @@ -112,6 +114,20 @@ export default class ShardManager extends Collection { if (this.options.getAllUsers && !(this.options.intents & Intents.GUILD_MEMBERS)) { throw new TypeError("Guild members cannot be requested without the GUILD_MEMBERS intent"); } + Object.defineProperties(this, { + client: { + value: client, + writable: false, + enumerable: false, + configurable: false + }, + dispatcher: { + value: new Dispatcher(this), + writable: false, + enumerable: false, + configurable: false + } + }); } @@ -124,7 +140,7 @@ export default class ShardManager extends Collection { if (this.options.maxShards === -1) { return undefined; } - return this.get((this._client.guildShardMap[guild] ??= Number((BigInt(guild) >> 22n) % BigInt(this.options.maxShards)))); + return this.get((this.client.guildShardMap[guild] ??= Number((BigInt(guild) >> 22n) % BigInt(this.options.maxShards)))); } private async _gatewayURLForShard(shard: Shard): Promise { @@ -137,7 +153,7 @@ export default class ShardManager extends Collection { } // how did we manage to get all the way to connecting without gatewayURL being set? - return (this._gatewayURL = (await (this.options.override.getBot?.() ?? this._client.rest.getBotGateway())).url); + return (this._gatewayURL = (await (this.options.override.getBot?.() ?? this.client.rest.getBotGateway())).url); } private _ready(id: number): void { @@ -160,10 +176,10 @@ export default class ShardManager extends Collection { const overrideURL = (this.options.override.getBot || this.options.override.url) !== undefined; try { if (this.options.maxShards === -1 || this.options.concurrency === -1) { - data = await (this.options.override.getBot?.() ?? this._client.rest.getBotGateway()); + data = await (this.options.override.getBot?.() ?? this.client.rest.getBotGateway()); url = data.url; } else { - url = overrideURL ? null : (await this._client.rest.getGateway()).url; + url = overrideURL ? null : (await this.client.rest.getGateway()).url; } } catch (err) { throw new TypeError("Failed to get gateway information.", { cause: err as Error }); @@ -179,10 +195,10 @@ export default class ShardManager extends Collection { /* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */ if (this.options.removeDisallowedIntents && PrivilegedIntentMapping.some(([intent]) => (this.options.intents & intent) === intent)) { - const { flags } = await this._client.rest.applications.getCurrent(); + const { flags } = await this.client.rest.applications.getCurrent(); const check = (intent: Intents, allowed: Array): void => { if ((this.options.intents & intent) === intent && !allowed.some(flag => (flags & flag) === flag)) { - this._client.emit("warn", `removeDisallowedIntents is enabled, and ${Intents[intent]} was included but not found to be allowed. It has been removed.`); + this.client.emit("warn", `removeDisallowedIntents is enabled, and ${Intents[intent]} was included but not found to be allowed. It has been removed.`); this.options.intents &= ~intent; } }; @@ -238,7 +254,7 @@ export default class ShardManager extends Collection { this.connected = false; } - this._client.ready = false; + this.client.ready = false; for (const [,shard] of this) shard.disconnect(reconnect); this._resetConnectQueue(); } @@ -246,12 +262,12 @@ export default class ShardManager extends Collection { spawn(id: number): void { let shard = this.get(id); if (!shard) { - shard = new Shard(id, this._client); + shard = new Shard(id, this); this.set(id, shard); shard .on("ready", () => { - this._client.emit("shardReady", id); - if (this._client.ready) { + this.client.emit("shardReady", id); + if (this.client.ready) { return; } for (const other of this.values()) { @@ -260,13 +276,13 @@ export default class ShardManager extends Collection { } } - this._client.ready = true; - this._client.startTime = Date.now(); - this._client.emit("ready"); + this.client.ready = true; + this.client.startTime = Date.now(); + this.client.emit("ready"); }) .on("resume", () => { - this._client.emit("shardResume", id); - if (this._client.ready) { + this.client.emit("shardResume", id); + if (this.client.ready) { return; } for (const other of this.values()) { @@ -275,24 +291,24 @@ export default class ShardManager extends Collection { } } - this._client.ready = true; - this._client.startTime = Date.now(); - this._client.emit("ready"); + this.client.ready = true; + this.client.startTime = Date.now(); + this.client.emit("ready"); }) .on("disconnect", error => { - this._client.emit("shardDisconnect", error, id); + this.client.emit("shardDisconnect", error, id); for (const other of this.values()) { if (other.ready) { return; } } - this._client.ready = false; - this._client.startTime = 0; - this._client.emit("disconnect"); + this.client.ready = false; + this.client.startTime = 0; + this.client.emit("disconnect"); }) .on("preReady", () => { - this._client.emit("shardPreReady", id); + this.client.emit("shardPreReady", id); }); } diff --git a/lib/gateway/events.ts b/lib/gateway/events.ts new file mode 100644 index 00000000..d97f261a --- /dev/null +++ b/lib/gateway/events.ts @@ -0,0 +1,956 @@ +import type { DispatchEventMap } from "./Dispatcher"; +import type Shard from "./Shard"; +import { ChannelTypes } from "../Constants"; +import type { PresenceUpdate } from "../types/gateway"; +import Member from "../structures/Member"; +import AutoModerationRule from "../structures/AutoModerationRule"; +import Channel from "../structures/Channel"; +import type { + AnyGuildChannelWithoutThreads, + AnyTextableChannel, + AnyThreadChannel, + AnyInviteChannel, + PossiblyUncachedInvite, + RawMessage, + ThreadMember, + ThreadParentChannel, + UncachedThreadMember, + AnyVoiceChannel, + PollAnswer, + AnyGuildChannel +} from "../types/channels"; +import type { JSONAnnouncementThreadChannel } from "../types/json"; +import VoiceChannel from "../structures/VoiceChannel"; +import StageChannel from "../structures/StageChannel"; +import GuildScheduledEvent from "../structures/GuildScheduledEvent"; +import Invite from "../structures/Invite"; +import Message from "../structures/Message"; +import StageInstance from "../structures/StageInstance"; +import type AnnouncementThreadChannel from "../structures/AnnouncementThreadChannel"; +import Interaction from "../structures/Interaction"; +import Guild from "../structures/Guild"; +import Role from "../structures/Role"; +import Integration from "../structures/Integration"; +import VoiceState from "../structures/VoiceState"; +import AuditLogEntry from "../structures/AuditLogEntry"; +import type User from "../structures/User"; + +export async function APPLICATION_COMMAND_PERMISSIONS_UPDATE(data: DispatchEventMap["APPLICATION_COMMAND_PERMISSIONS_UPDATE"], shard: Shard): Promise { + shard.client.emit("applicationCommandPermissionsUpdate", shard.client.guilds.get(data.guild_id) ?? { id: data.guild_id }, { + application: data.application_id === shard.client.application.id ? shard.client.application : undefined, + applicationID: data.application_id, + id: data.id, + permissions: data.permissions + }); +} + +export async function AUTO_MODERATION_ACTION_EXECUTION(data: DispatchEventMap["AUTO_MODERATION_ACTION_EXECUTION"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const channel = shard.client.getChannel(data.channel_id ?? ""); + shard.client.emit( + "autoModerationActionExecution", + guild ?? { id: data.guild_id }, + data.channel_id === undefined ? null : channel ?? { id: data.channel_id }, + shard.client.users.get(data.user_id) ?? { id: data.user_id }, + { + action: { + metadata: { + channelID: data.action.metadata.channel_id, + customMessage: data.action.metadata.custom_message, + durationSeconds: data.action.metadata.duration_seconds + }, + type: data.action.type + }, + alertSystemMessageID: data.alert_system_message_id, + content: data.content, + matchedContent: data.matched_content, + matchedKeyword: data.matched_keyword, + messageID: data.message_id, + rule: guild?.autoModerationRules.get(data.rule_id), + ruleID: data.rule_id, + ruleTriggerType: data.rule_trigger_type + } + ); +} + +export async function AUTO_MODERATION_RULE_CREATE(data: DispatchEventMap["AUTO_MODERATION_RULE_CREATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const rule = guild?.autoModerationRules.update(data) ?? new AutoModerationRule(data, shard.client); + shard.client.emit("autoModerationRuleCreate", rule); +} + +export async function AUTO_MODERATION_RULE_DELETE(data: DispatchEventMap["AUTO_MODERATION_RULE_DELETE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const rule = guild?.autoModerationRules.update(data) ?? new AutoModerationRule(data, shard.client); + guild?.autoModerationRules.delete(data.id); + shard.client.emit("autoModerationRuleDelete", rule); +} + +export async function AUTO_MODERATION_RULE_UPDATE(data: DispatchEventMap["AUTO_MODERATION_RULE_UPDATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const oldRule = guild?.autoModerationRules.get(data.id)?.toJSON() ?? null; + const rule = guild?.autoModerationRules.update(data) ?? new AutoModerationRule(data, shard.client); + shard.client.emit("autoModerationRuleUpdate", rule, oldRule); + shard.client.emit("autoModerationRuleUpdate", rule, oldRule); +} + +export async function CHANNEL_CREATE(data: DispatchEventMap["CHANNEL_CREATE"], shard: Shard): Promise { + const channel = shard.client.util.updateChannel(data); + shard.client.emit("channelCreate", channel); +} + +export async function CHANNEL_DELETE(data: DispatchEventMap["CHANNEL_DELETE"], shard: Shard): Promise { + if (data.type === ChannelTypes.DM) { + const channel = shard.client.privateChannels.get(data.id); + shard.client.privateChannels.delete(data.id); + shard.client.emit("channelDelete", channel ?? { + id: data.id, + flags: data.flags, + lastMessageID: data.last_message_id, + type: data.type + }); + return; + } + const guild = shard.client.guilds.get(data.guild_id); + const channel = shard.client.util.updateChannel(data); + if (channel instanceof VoiceChannel || channel instanceof StageChannel) { + for (const [,member] of channel.voiceMembers) { + channel.voiceMembers.delete(member.id); + shard.client.emit("voiceChannelLeave", member, channel); + } + } + guild?.channels.delete(data.id); + shard.client.emit("channelDelete", channel); +} + +export async function CHANNEL_PINS_UPDATE(data: DispatchEventMap["CHANNEL_PINS_UPDATE"], shard: Shard): Promise { + const channel = shard.client.getChannel(data.channel_id); + shard.client.emit("channelPinsUpdate", channel ?? { id: data.channel_id }, data.last_pin_timestamp === undefined || data.last_pin_timestamp === null ? null : new Date(data.last_pin_timestamp)); +} + +export async function CHANNEL_UPDATE(data: DispatchEventMap["CHANNEL_UPDATE"], shard: Shard): Promise { + const oldChannel = shard.client.getChannel(data.id)?.toJSON() ?? null; + let channel: AnyGuildChannel; + if (oldChannel && oldChannel.type !== data.type) { + if (shard.client.channelGuildMap[data.id]) { + shard.client.guilds.get(shard.client.channelGuildMap[data.id])!.channels.delete(data.id); + } + + channel = shard.client.util.updateChannel(data); + } else { + channel = shard.client.util.updateChannel(data); + } + shard.client.emit("channelUpdate", channel, oldChannel); +} + +export async function ENTITLEMENT_CREATE(data: DispatchEventMap["ENTITLEMENT_CREATE"], shard: Shard): Promise { + const entitlement = shard.client.util.updateEntitlement(data); + shard.client.emit("entitlementCreate", entitlement); +} + +export async function ENTITLEMENT_DELETE(data: DispatchEventMap["ENTITLEMENT_DELETE"], shard: Shard): Promise { + const entitlement = shard.client.util.updateEntitlement(data); + shard.client["_application"]?.entitlements.delete(data.id); + shard.client.emit("entitlementDelete", entitlement); +} + +export async function ENTITLEMENT_UPDATE(data: DispatchEventMap["ENTITLEMENT_UPDATE"], shard: Shard): Promise { + const oldEntitlement = shard.client["_application"]?.entitlements.get(data.id)?.toJSON() ?? null; + const entitlement = shard.client.util.updateEntitlement(data); + shard.client.emit("entitlementUpdate", entitlement, oldEntitlement); +} + +export async function GUILD_AUDIT_LOG_ENTRY_CREATE(data: DispatchEventMap["GUILD_AUDIT_LOG_ENTRY_CREATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + shard.client.emit("guildAuditLogEntryCreate", guild ?? { id: data.guild_id }, guild?.auditLogEntries.update(data) ?? new AuditLogEntry(data, shard.client)); +} + +export async function GUILD_BAN_ADD(data: DispatchEventMap["GUILD_BAN_ADD"], shard: Shard): Promise { + shard.client.emit("guildBanAdd", shard.client.guilds.get(data.guild_id) ?? { id: data.guild_id }, shard.client.users.update(data.user)); +} + +export async function GUILD_BAN_REMOVE(data: DispatchEventMap["GUILD_BAN_REMOVE"], shard: Shard): Promise { + shard.client.emit("guildBanRemove", shard.client.guilds.get(data.guild_id) ?? { id: data.guild_id }, shard.client.users.update(data.user)); +} + +export async function GUILD_CREATE(data: DispatchEventMap["GUILD_CREATE"], shard: Shard): Promise { + + if (data.unavailable) { + shard.client.guilds.delete(data.id); + shard.client.emit("unavailableGuildCreate", shard.client.unavailableGuilds.update(data)); + } else { + const guild = shard["createGuild"](data); + if (shard.ready) { + if (shard.client.unavailableGuilds.delete(guild.id)) { + shard.client.emit("guildAvailable", guild); + } else { + shard.client.emit("guildCreate", guild); + } + } else { + if (shard.client.unavailableGuilds.delete(guild.id)) { + void shard["restartGuildCreateTimeout"](); + } else { + shard.client.emit("guildCreate", guild); + } + } + } +} + +export async function GUILD_DELETE(data: DispatchEventMap["GUILD_DELETE"], shard: Shard): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + shard.client.voiceAdapters.get(data.id)?.destroy(); + delete shard.client.guildShardMap[data.id]; + const guild = shard.client.guilds.get(data.id); + guild?.channels.clear(); + guild?.threads.clear(); + shard.client.guilds.delete(data.id); + if (data.unavailable) { + shard.client.emit("guildUnavailable", shard.client.unavailableGuilds.update(data)); + } else { + shard.client.emit("guildDelete", guild ?? { id: data.id }); + } +} + +export async function GUILD_EMOJIS_UPDATE(data: DispatchEventMap["GUILD_EMOJIS_UPDATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const oldEmojis = guild?.emojis ? guild.emojis.toArray() : null; + // eslint-disable-next-line @typescript-eslint/dot-notation + guild?.["update"]({ emojis: data.emojis }); + shard.client.emit( + "guildEmojisUpdate", + guild ?? { id: data.guild_id }, + guild?.emojis?.toArray() ?? data.emojis.map(emoji => shard.client.util.convertEmoji(emoji)), + oldEmojis + ); +} + +export async function GUILD_INTEGRATIONS_UPDATE(data: DispatchEventMap["GUILD_INTEGRATIONS_UPDATE"], shard: Shard): Promise { + shard.client.emit("guildIntegrationsUpdate", shard.client.guilds.get(data.guild_id) ?? { id: data.guild_id }); +} + +export async function GUILD_MEMBER_ADD(data: DispatchEventMap["GUILD_MEMBER_ADD"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + if (guild) { + guild.memberCount++; + } + const member = shard.client.util.updateMember(data.guild_id, data.user!.id, data); + shard.client.emit("guildMemberAdd", member); +} + +export async function GUILD_MEMBERS_CHUNK(data: DispatchEventMap["GUILD_MEMBERS_CHUNK"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + // eslint-disable-next-line @typescript-eslint/dot-notation + guild?.["updateMemberLimit"](data.members.length); + const members = data.members.map(member => shard.client.util.updateMember(data.guild_id, member.user!.id, member)); + if (data.presences) for (const presence of data.presences) { + const member = members.find(m => m.id === presence.user.id)!; + member.presence = { + clientStatus: presence.client_status, + guildID: presence.guild_id, + status: presence.status, + activities: presence.activities?.map(activity => ({ + createdAt: activity.created_at, + name: activity.name, + type: activity.type, + applicationID: activity.application_id, + assets: activity.assets ? { + largeImage: activity.assets.large_image, + largeText: activity.assets.large_text, + smallImage: activity.assets.small_image, + smallText: activity.assets.small_text + } : undefined, + buttons: activity.buttons, + details: activity.details, + emoji: activity.emoji, + flags: activity.flags, + instance: activity.instance, + party: activity.party, + secrets: activity.secrets, + state: activity.state, + timestamps: activity.timestamps, + url: activity.url + })) + }; + } + if (!data.nonce) { + shard.client.emit("warn", "Received GUILD_MEMBERS_CHUNK without a nonce."); + return; + } + if (shard["_requestMembersPromise"][data.nonce]) { + shard["_requestMembersPromise"][data.nonce].members.push(...members); + } + + if (data.chunk_index >= data.chunk_count - 1) { + if (shard["_requestMembersPromise"][data.nonce]) { + clearTimeout(shard["_requestMembersPromise"][data.nonce].timeout); + shard["_requestMembersPromise"][data.nonce].resolve(shard["_requestMembersPromise"][data.nonce].members); + delete shard["_requestMembersPromise"][data.nonce]; + } + if (shard["_getAllUsersCount"][data.guild_id]) { + delete shard["_getAllUsersCount"][data.guild_id]; + void shard["checkReady"](); + } + } + + shard.client.emit("guildMemberChunk", members); + shard.lastHeartbeatAck = true; +} + +export async function GUILD_MEMBER_REMOVE(data: DispatchEventMap["GUILD_MEMBER_REMOVE"], shard: Shard): Promise { + if (data.user.id === shard.client.user.id) { + return; + } + const guild = shard.client.guilds.get(data.guild_id); + // eslint-disable-next-line @typescript-eslint/dot-notation + let user: Member | User | undefined = guild?.members.get(data.user.id); + if (user instanceof Member) { + user["update"]({ user: data.user }); + } else { + user = shard.client.users.update(data.user); + } + if (guild) { + guild.memberCount--; + guild.members.delete(data.user.id); + } + shard.client.emit("guildMemberRemove", user, guild ?? { id: data.guild_id }); +} + +export async function GUILD_MEMBER_UPDATE(data: DispatchEventMap["GUILD_MEMBER_UPDATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const oldMember = guild?.members.get(data.user.id)?.toJSON() ?? null; + const member = shard.client.util.updateMember(data.guild_id, data.user.id, { deaf: oldMember?.deaf ?? false, mute: oldMember?.mute ?? false, ...data }); + shard.client.emit("guildMemberUpdate", member, oldMember); +} + +export async function GUILD_ROLE_CREATE(data: DispatchEventMap["GUILD_ROLE_CREATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const role = guild?.roles.update(data.role, data.guild_id) ?? new Role(data.role, shard.client, data.guild_id); + shard.client.emit("guildRoleCreate", role); +} + +export async function GUILD_ROLE_DELETE(data: DispatchEventMap["GUILD_ROLE_DELETE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const role = guild?.roles.get(data.role_id); + guild?.roles.delete(data.role_id); + shard.client.emit("guildRoleDelete", role ?? { id: data.role_id }, guild ?? { id: data.guild_id }); +} + +export async function GUILD_ROLE_UPDATE(data: DispatchEventMap["GUILD_ROLE_UPDATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const oldRole = guild?.roles.get(data.role.id)?.toJSON() ?? null; + const role = guild?.roles.update(data.role, data.guild_id) ?? new Role(data.role, shard.client, data.guild_id); + shard.client.emit("guildRoleUpdate", role, oldRole); +} + +export async function GUILD_SCHEDULED_EVENT_CREATE(data: DispatchEventMap["GUILD_SCHEDULED_EVENT_CREATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const event = guild?.scheduledEvents.update(data) ?? new GuildScheduledEvent(data, shard.client); + shard.client.emit("guildScheduledEventCreate", event); +} + +export async function GUILD_SCHEDULED_EVENT_DELETE(data: DispatchEventMap["GUILD_SCHEDULED_EVENT_DELETE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const event = guild?.scheduledEvents.update(data) ?? new GuildScheduledEvent(data, shard.client); + guild?.scheduledEvents.delete(data.id); + shard.client.emit("guildScheduledEventDelete", event); +} + +export async function GUILD_SCHEDULED_EVENT_UPDATE(data: DispatchEventMap["GUILD_SCHEDULED_EVENT_UPDATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id)!; + const oldEvent = guild?.scheduledEvents.get(data.id)?.toJSON() ?? null; + const event = guild?.scheduledEvents.update(data) ?? new GuildScheduledEvent(data, shard.client); + shard.client.emit("guildScheduledEventUpdate", event, oldEvent); +} + +export async function GUILD_SCHEDULED_EVENT_USER_ADD(data: DispatchEventMap["GUILD_SCHEDULED_EVENT_USER_ADD"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const event = guild?.scheduledEvents.get(data.guild_scheduled_event_id); + if (event?.userCount) { + event.userCount++; + } + const user = shard.client.users.get(data.user_id) ?? { id: data.user_id }; + shard.client.emit("guildScheduledEventUserAdd", event ?? { id: data.guild_scheduled_event_id }, user ?? { id: data.user_id }); +} + +export async function GUILD_SCHEDULED_EVENT_USER_REMOVE(data: DispatchEventMap["GUILD_SCHEDULED_EVENT_USER_REMOVE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const event = guild?.scheduledEvents.get(data.guild_scheduled_event_id); + if (event?.userCount) { + event.userCount--; + } + const user = shard.client.users.get(data.user_id) ?? { id: data.user_id }; + shard.client.emit("guildScheduledEventUserRemove", event ?? { id: data.guild_scheduled_event_id }, user ?? { id: data.user_id }); +} + +export async function GUILD_STICKERS_UPDATE(data: DispatchEventMap["GUILD_STICKERS_UPDATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const oldStickers = guild?.stickers ? guild.stickers.toArray() : null; + // eslint-disable-next-line @typescript-eslint/dot-notation + guild?.["update"]({ stickers: data.stickers }); + shard.client.emit("guildStickersUpdate", guild ?? { id: data.guild_id }, guild?.stickers?.toArray() ?? data.stickers.map(sticker => shard.client.util.convertSticker(sticker)), oldStickers); +} + +export async function GUILD_UPDATE(data: DispatchEventMap["GUILD_UPDATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.id); + const oldGuild = guild?.toJSON() ?? null; + shard.client.emit("guildUpdate", shard.client.guilds.update(data), oldGuild); +} + +export async function INTEGRATION_CREATE(data: DispatchEventMap["INTEGRATION_CREATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const integration = guild?.integrations.update(data, data.guild_id) ?? new Integration(data, shard.client, data.guild_id); + shard.client.emit("integrationCreate", guild ?? { id: data.guild_id }, integration); +} + +export async function INTEGRATION_DELETE(data: DispatchEventMap["INTEGRATION_DELETE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const integration = guild?.integrations.get(data.id); + guild?.integrations.delete(data.id); + shard.client.emit("integrationDelete", guild ?? { id: data.guild_id }, integration ?? { applicationID: data.application_id, id: data.id }); +} + +export async function INTEGRATION_UPDATE(data: DispatchEventMap["INTEGRATION_UPDATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const oldIntegration = guild?.integrations.get(data.id)?.toJSON() ?? null; + const integration = guild?.integrations.update(data, data.guild_id) ?? new Integration(data, shard.client, data.guild_id); + shard.client.emit("integrationUpdate", guild ?? { id: data.guild_id }, integration, oldIntegration); +} + +export async function INTERACTION_CREATE(data: DispatchEventMap["INTERACTION_CREATE"], shard: Shard): Promise { + shard.client.emit("interactionCreate", Interaction.from(data, shard.client)); +} + +export async function INVITE_CREATE(data: DispatchEventMap["INVITE_CREATE"], shard: Shard): Promise { + let invite: Invite | undefined; + if (data.guild_id) { + const guild = shard.client.guilds.get(data.guild_id); + invite = guild?.invites.update(data); + } + shard.client.emit("inviteCreate", invite ?? new Invite(data, shard.client)); +} + +export async function INVITE_DELETE(data: DispatchEventMap["INVITE_DELETE"], shard: Shard): Promise { + const channel = shard.client.getChannel(data.channel_id) ?? { id: data.channel_id }; + const guild = data.guild_id ? shard.client.guilds.get(data.guild_id) ?? { id: data.guild_id } : undefined; + let invite: PossiblyUncachedInvite = { + code: data.code, + channel, + guild + }; + if (guild instanceof Guild && guild.invites.has(data.code)) { + invite = guild.invites.get(data.code)!; + guild.invites.delete(data.code); + } + shard.client.emit("inviteDelete", invite); +} + +export async function MESSAGE_CREATE(data: DispatchEventMap["MESSAGE_CREATE"], shard: Shard): Promise { + const channel = shard.client.getChannel(data.channel_id); + const message = channel?.messages?.update(data) ?? new Message(data, shard.client); + if (channel) { + channel.lastMessage = message as never; + channel.lastMessageID = message.id; + } + shard.client.emit("messageCreate", message); +} + +export async function MESSAGE_DELETE(data: DispatchEventMap["MESSAGE_DELETE"], shard: Shard): Promise { + const channel = shard.client.getChannel(data.channel_id); + const message = channel?.messages?.get(data.id); + if (channel) { + channel.messages?.delete(data.id); + if (channel.lastMessageID === data.id) { + channel.lastMessageID = null; + channel.lastMessage = null; + } + } + shard.client.emit("messageDelete", message ?? { + channel: channel ?? { id: data.channel_id }, + channelID: data.channel_id, + guild: data.guild_id ? shard.client.guilds.get(data.guild_id) : undefined, + guildID: data.guild_id, id: data.id + }); +} + +export async function MESSAGE_DELETE_BULK(data: DispatchEventMap["MESSAGE_DELETE_BULK"], shard: Shard): Promise { + const channel = shard.client.getChannel(data.channel_id); + const guild = data.guild_id ? shard.client.guilds.get(data.guild_id) : undefined; + shard.client.emit("messageDeleteBulk", data.ids.map(id => { + const message = channel?.messages?.get(id); + channel?.messages?.delete(id); + return message ?? { + channel: channel ?? { id: data.channel_id }, + channelID: data.channel_id, + guild, + guildID: data.guild_id, + id + }; + })); +} + +export async function MESSAGE_POLL_VOTE_ADD(data: DispatchEventMap["MESSAGE_POLL_VOTE_ADD"], shard: Shard): Promise { + const user = shard.client.users.get(data.user_id) ?? { id: data.user_id }; + const channel = shard.client.getChannel(data.channel_id) ?? { id: data.channel_id }; + const guild = data.guild_id ? shard.client.guilds.get(data.guild_id) : undefined; + const message = (channel instanceof Channel ? channel.messages.get(data.message_id) : undefined) ?? { channel, channelID: channel.id, guild, guildID: guild?.id, id: data.message_id }; + let answer: PollAnswer | { answerID: number; } = { answerID: data.answer_id }; + if (message instanceof Message && message.poll !== undefined) { + const pollAnswer = message.poll.answers.find(a => a.answerID === data.answer_id); + if (pollAnswer) { + answer = pollAnswer; + } + + shard.client.util.updatePollAnswer(message.poll, data.answer_id, 1, data.user_id); + } + shard.client.emit("messagePollVoteAdd", message, user, answer); +} + +export async function MESSAGE_POLL_VOTE_REMOVE(data: DispatchEventMap["MESSAGE_POLL_VOTE_REMOVE"], shard: Shard): Promise { + const user = shard.client.users.get(data.user_id) ?? { id: data.user_id }; + const channel = shard.client.getChannel(data.channel_id) ?? { id: data.channel_id }; + const guild = data.guild_id ? shard.client.guilds.get(data.guild_id) : undefined; + const message = (channel instanceof Channel ? channel.messages.get(data.message_id) : undefined) ?? { channel, channelID: channel.id, guild, guildID: guild?.id, id: data.message_id }; + let answer: PollAnswer | { answerID: number; } = { answerID: data.answer_id }; + if (message instanceof Message && message.poll !== undefined) { + const pollAnswer = message.poll.answers.find(a => a.answerID === data.answer_id); + if (pollAnswer) { + answer = pollAnswer; + } + + shard.client.util.updatePollAnswer(message.poll, data.answer_id, -1, data.user_id); + } + shard.client.emit("messagePollVoteRemove", message, user, answer); +} + +export async function MESSAGE_REACTION_ADD(data: DispatchEventMap["MESSAGE_REACTION_ADD"], shard: Shard): Promise { + const channel = shard.client.getChannel(data.channel_id); + const guild = data.guild_id ? shard.client.guilds.get(data.guild_id) : undefined; + const message = channel?.messages?.get(data.message_id); + const reactor = data.member + ? (data.guild_id ? shard.client.util.updateMember(data.guild_id, data.user_id, data.member) : shard.client.users.get(data.user_id) ?? { id: data.user_id }) + : shard.client.users.get(data.user_id) ?? { id: data.user_id }; + + if (message) { + const index = message.reactions.findIndex(r => r.emoji.id === data.emoji.id && r.emoji.name === data.emoji.name); + if (index === -1) { + message.reactions.push({ + burstColors: data.burst_colors, + count: 1, + countDetails: { + burst: data.burst ? 1 : 0, + normal: data.burst ? 0 : 1 + }, + emoji: data.emoji, + me: data.user_id === shard.client.user.id, + meBurst: data.user_id === shard.client.user.id && data.burst + }); + } else { + if (data.burst) { + message.reactions[index].countDetails.burst++; + } else { + message.reactions[index].countDetails.normal++; + } + message.reactions[index].count++; + if (data.user_id === shard.client.user.id) { + message.reactions[index].me = true; + } + } + + } + + shard.client.emit("messageReactionAdd", message ?? { + channel: channel ?? { id: data.channel_id }, + channelID: data.channel_id, + guild, + guildID: data.guild_id, + id: data.message_id , + author: data.message_author_id === undefined ? undefined : shard.client.users.get(data.message_author_id) ?? { id: data.message_author_id }, + member: data.message_author_id === undefined ? undefined : guild?.members.get(data.message_author_id) ?? { id: data.message_author_id } + }, reactor, { + burst: data.burst, + burstColors: data.burst_colors, + emoji: data.emoji, + type: data.type + }); +} + +export async function MESSAGE_REACTION_REMOVE(data: DispatchEventMap["MESSAGE_REACTION_REMOVE"], shard: Shard): Promise { + const channel = shard.client.getChannel(data.channel_id); + const message = channel?.messages?.get(data.message_id); + const reactor = shard.client.users.get(data.user_id) ?? { id: data.user_id }; + + if (message) { + const index = message.reactions.findIndex(r => r.emoji.id === data.emoji.id && r.emoji.name === data.emoji.name); + if (index !== -1) { + if (data.burst) { + message.reactions[index].countDetails.burst--; + } else { + message.reactions[index].countDetails.normal--; + } + message.reactions[index].count--; + if (data.user_id === shard.client.user.id) { + if (data.burst) { + message.reactions[index].meBurst = false; + } else { + message.reactions[index].me = false; + } + } + if (message.reactions[index].count === 0) { + message.reactions.splice(index, 1); + } + } + } + + shard.client.emit("messageReactionRemove", message ?? { + channel: channel ?? { id: data.channel_id }, + channelID: data.channel_id, + guild: data.guild_id ? shard.client.guilds.get(data.guild_id) : undefined, + guildID: data.guild_id, + id: data.message_id + }, reactor, { + burst: data.burst, + burstColors: data.burst_colors, + emoji: data.emoji, + type: data.type + }); +} + +export async function MESSAGE_REACTION_REMOVE_ALL(data: DispatchEventMap["MESSAGE_REACTION_REMOVE_ALL"], shard: Shard): Promise { + const channel = shard.client.getChannel(data.channel_id); + const message = channel?.messages?.get(data.message_id); + + if (message) { + message.reactions = []; + } + + shard.client.emit("messageReactionRemoveAll", message ?? { + channel: channel ?? { id: data.channel_id }, + channelID: data.channel_id, + guild: data.guild_id ? shard.client.guilds.get(data.guild_id) : undefined, + guildID: data.guild_id, + id: data.message_id + }); +} + +export async function MESSAGE_REACTION_REMOVE_EMOJI(data: DispatchEventMap["MESSAGE_REACTION_REMOVE_EMOJI"], shard: Shard): Promise { + const channel = shard.client.getChannel(data.channel_id); + const message = channel?.messages?.get(data.message_id); + + if (message) { + const index = message.reactions.findIndex(r => r.emoji.id === data.emoji.id && r.emoji.name === data.emoji.name); + if (index !== -1) { + message.reactions.splice(index, 1); + } + } + + shard.client.emit("messageReactionRemoveEmoji", message ?? { + channel: channel ?? { id: data.channel_id }, + channelID: data.channel_id, + guild: data.guild_id ? shard.client.guilds.get(data.guild_id) : undefined, + guildID: data.guild_id, + id: data.message_id + }, data.emoji); +} + +export async function MESSAGE_UPDATE(data: DispatchEventMap["MESSAGE_UPDATE"], shard: Shard): Promise { + const channel = shard.client.getChannel(data.channel_id); + const oldMessage = channel?.messages?.get(data.id)?.toJSON() ?? null; + if (!oldMessage && !data.author) { + shard.client.emit("debug", `Got partial MESSAGE_UPDATE for uncached message ${data.id} for channel ${data.channel_id}, discarding..`); + return; + } + const message = channel?.messages?.update(data) ?? new Message(data as RawMessage, shard.client); + shard.client.emit("messageUpdate", message, oldMessage); +} + +export async function PRESENCE_UPDATE(data: DispatchEventMap["PRESENCE_UPDATE"], shard: Shard): Promise { + const user = shard.client.users.get(data.user.id); + if (user) { + const oldUser = user.toJSON(); + user["update"](data.user); + if (JSON.stringify(oldUser) !== JSON.stringify(user.toJSON())) { + shard.client.emit("userUpdate", user, oldUser); + } + } + + const guild = shard.client.guilds.get(data.guild_id); + const member = guild?.members.get(data.user.id); + const oldPresence = member?.presence ?? null; + + const presence = { + clientStatus: data.client_status, + guildID: data.guild_id, + status: data.status, + activities: data.activities?.map(activity => ({ + createdAt: activity.created_at, + name: activity.name, + type: activity.type, + applicationID: activity.application_id, + assets: activity.assets ? { + largeImage: activity.assets.large_image, + largeText: activity.assets.large_text, + smallImage: activity.assets.small_image, + smallText: activity.assets.small_text + } : undefined, + buttons: activity.buttons, + details: activity.details, + emoji: activity.emoji, + flags: activity.flags, + instance: activity.instance, + party: activity.party, + secrets: activity.secrets, + state: activity.state, + timestamps: activity.timestamps, + url: activity.url + })) + }; + const userID = data.user.id; + + delete (data as { user?: PresenceUpdate["user"]; }).user; + if (member) { + member.presence = presence; + } + + shard.client.emit("presenceUpdate", guild ?? { id: data.guild_id }, member ?? { id: userID }, presence, oldPresence); +} + +export async function READY(data: DispatchEventMap["READY"], shard: Shard): Promise { + shard["_ready"](data); +} + +export async function RESUMED(data: DispatchEventMap["RESUMED"], shard: Shard): Promise { + shard["_resume"](); +} + +export async function STAGE_INSTANCE_CREATE(data: DispatchEventMap["STAGE_INSTANCE_CREATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const stateInstance = guild?.stageInstances.update(data) ?? new StageInstance(data, shard.client); + shard.client.emit("stageInstanceCreate", stateInstance); +} + +export async function STAGE_INSTANCE_DELETE(data: DispatchEventMap["STAGE_INSTANCE_DELETE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const stateInstance = guild?.stageInstances.update(data) ?? new StageInstance(data, shard.client); + guild?.stageInstances.delete(data.id); + shard.client.emit("stageInstanceDelete", stateInstance); +} + +export async function STAGE_INSTANCE_UPDATE(data: DispatchEventMap["STAGE_INSTANCE_UPDATE"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + const oldStageInstance = guild?.stageInstances.get(data.id)?.toJSON() ?? null; + const stateInstance = guild?.stageInstances.update(data) ?? new StageInstance(data, shard.client); + shard.client.emit("stageInstanceUpdate", stateInstance, oldStageInstance); +} + +export async function THREAD_CREATE(data: DispatchEventMap["THREAD_CREATE"], shard: Shard): Promise { + const thread = shard.client.util.updateThread(data); + const channel = shard.client.getChannel(data.parent_id!); + if (channel && channel.type === ChannelTypes.GUILD_FORUM) { + channel.lastThreadID = thread.id; + } + shard.client.emit("threadCreate", thread); +} + +export async function THREAD_DELETE(data: DispatchEventMap["THREAD_DELETE"], shard: Shard): Promise { + const channel = shard.client.getChannel(data.parent_id!); + const thread = shard.client.getChannel(data.id) ?? { + id: data.id, + guild: shard.client.guilds.get(data.guild_id), + guildID: data.guild_id, + parent: channel || { id: data.parent_id! }, + parentID: data.parent_id!, + type: data.type + }; + if (channel && channel.type === ChannelTypes.GUILD_FORUM && channel.lastThreadID === data.id) { + channel.lastThreadID = null; + } + shard.client.guilds.get(data.guild_id)?.threads.delete(data.id); + shard.client.emit("threadDelete", thread); +} + +export async function THREAD_LIST_SYNC(data: DispatchEventMap["THREAD_LIST_SYNC"], shard: Shard): Promise { + const guild = shard.client.guilds.get(data.guild_id); + if (!guild) { + shard.client.emit("debug", `Missing guild in THREAD_LIST_SYNC: ${data.guild_id}`); + return; + } + for (const threadData of data.threads) { + shard.client.util.updateThread(threadData); + } + for (const member of data.members) { + const thread = shard.client.getChannel(member.id); + if (thread) { + const threadMember: ThreadMember = { + id: member.id, + flags: member.flags, + joinTimestamp: new Date(member.join_timestamp), + userID: member.user_id + }; + const index = thread.members.findIndex(m => m.userID === member.user_id); + if (index === -1) { + thread.members.push(threadMember); + } else { + thread.members[index] = threadMember; + } + } + } +} + +export async function THREAD_MEMBER_UPDATE(data: DispatchEventMap["THREAD_MEMBER_UPDATE"], shard: Shard): Promise { + const thread = shard.client.getChannel(data.id); + const guild = shard.client.guilds.get(data.guild_id); + const threadMember: ThreadMember = { + id: data.id, + flags: data.flags, + joinTimestamp: new Date(data.join_timestamp), + userID: data.user_id + }; + let oldThreadMember: ThreadMember | null = null; + if (thread) { + const index = thread.members.findIndex(m => m.userID === data.user_id); + if (index === -1) { + thread.members.push(threadMember); + } else { + oldThreadMember = { ...thread.members[index] }; + thread.members[index] = threadMember; + } + } + + shard.client.emit( + "threadMemberUpdate", + thread ?? { + id: data.id, + guild, + guildID: data.guild_id + }, + threadMember, + oldThreadMember + ); +} + +export async function THREAD_MEMBERS_UPDATE(data: DispatchEventMap["THREAD_MEMBERS_UPDATE"], shard: Shard): Promise { + const thread = shard.client.getChannel(data.id); + const guild = shard.client.guilds.get(data.guild_id); + const addedMembers: Array = (data.added_members ?? []).map(rawMember => ({ + flags: rawMember.flags, + id: rawMember.id, + joinTimestamp: new Date(rawMember.join_timestamp), + userID: rawMember.user_id + })); + const removedMembers: Array = (data.removed_member_ids ?? []).map(id => ({ userID: id, id: data.id })); + if (thread) { + thread.memberCount = data.member_count; + for (const rawMember of addedMembers) { + const index = thread.members.findIndex(m => m.userID === rawMember.id); + if (index === -1) { + thread.members.push(rawMember); + } else { + thread.members[index] = rawMember; + } + } + for (const [index, { userID }] of removedMembers.entries()) { + const memberIndex = thread.members.findIndex(m => m.userID === userID); + if (memberIndex >= 0) { + removedMembers[index] = thread.members[memberIndex]; + thread.members.splice(memberIndex, 1); + } + } + } + shard.client.emit( + "threadMembersUpdate", + thread ?? { + id: data.id, + guild, + guildID: data.guild_id + }, + addedMembers, + removedMembers + ); +} + +export async function THREAD_UPDATE(data: DispatchEventMap["THREAD_UPDATE"], shard: Shard): Promise { + const oldThread = shard.client.getChannel(data.id)?.toJSON() ?? null; + const thread = shard.client.util.updateThread(data); + shard.client.emit("threadUpdate", thread as AnnouncementThreadChannel, oldThread as JSONAnnouncementThreadChannel); +} + +export async function TYPING_START(data: DispatchEventMap["TYPING_START"], shard: Shard): Promise { + const channel = shard.client.getChannel(data.channel_id) ?? { id: data.channel_id }; + const startTimestamp = new Date(data.timestamp); + if (data.member) { + const member = shard.client.util.updateMember(data.guild_id!, data.user_id, data.member); + shard.client.emit("typingStart", channel, member, startTimestamp); + return; + } + const user = shard.client.users.get(data.user_id); + shard.client.emit("typingStart", channel, user ?? { id: data.user_id }, startTimestamp); +} + +export async function USER_UPDATE(data: DispatchEventMap["USER_UPDATE"], shard: Shard): Promise { + const oldUser = shard.client.users.get(data.id)?.toJSON() ?? null; + shard.client.emit("userUpdate", shard.client.users.update(data), oldUser); +} + +export async function VOICE_CHANNEL_EFFECT_SEND(data: DispatchEventMap["VOICE_CHANNEL_EFFECT_SEND"], shard: Shard): Promise { + const channel = shard.client.getChannel(data.channel_id); + const guild = shard.client.guilds.get(data.guild_id); + const user = guild?.members.get(data.user_id) ?? shard.client.users.get(data.user_id); + shard.client.emit("voiceChannelEffectSend", channel ?? { id: data.channel_id, guild: guild ?? { id: data.guild_id } }, user ?? { id: data.user_id }, { + animationID: data.animation_id, + animationType: data.animation_type + }); +} + +export async function VOICE_STATE_UPDATE(data: DispatchEventMap["VOICE_STATE_UPDATE"], shard: Shard): Promise { + if (data.guild_id && data.session_id && data.user_id === shard.client.user.id) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + shard.client.voiceAdapters.get(data.guild_id)?.onVoiceStateUpdate(data as never); + } + // @TODO voice states without guilds? + if (!data.guild_id || !data.member) { + return; + } + data.self_stream = !!data.self_stream; + const guild = shard.client.guilds.get(data.guild_id); + const member = shard.client.util.updateMember(data.guild_id, data.user_id, data.member); + + const oldState = guild?.voiceStates.get(member.id)?.toJSON() ?? null; + const state = guild?.voiceStates.update({ ...data, id: member.id }) ?? new VoiceState(data, shard.client); + member["update"]({ deaf: state.deaf, mute: state.mute }); + + if (oldState?.channelID !== state.channelID) { + const oldChannel = oldState?.channelID ? shard.client.getChannel(oldState.channelID) ?? { id: oldState.channelID } : null; + const newChannel = state.channel === null ? null : state.channel ?? { id: state.channelID! }; + + if (newChannel instanceof Channel) { + newChannel.voiceMembers.add(member); + } + if (oldChannel instanceof Channel) { + oldChannel.voiceMembers.delete(member.id); + } + if (oldChannel && newChannel) { + shard.client.emit("voiceChannelSwitch", member, newChannel, oldChannel); + } else if (newChannel) { + shard.client.emit("voiceChannelJoin", member, newChannel); + } else if (state.channelID === null) { + shard.client.emit("voiceChannelLeave", member, oldChannel); + } + } + + if (JSON.stringify(oldState) !== JSON.stringify(state.toJSON())) { + shard.client.emit("voiceStateUpdate", member, oldState); + } +} + +export async function VOICE_CHANNEL_STATUS_UPDATE(data: DispatchEventMap["VOICE_CHANNEL_STATUS_UPDATE"], shard: Shard): Promise { + shard.client.emit("voiceChannelStatusUpdate", shard.client.getChannel(data.id) ?? { id: data.id }, data.status); +} + +export async function VOICE_SERVER_UPDATE(data: DispatchEventMap["VOICE_SERVER_UPDATE"], shard: Shard): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + shard.client.voiceAdapters.get(data.guild_id)?.onVoiceServerUpdate(data); +} + +export async function WEBHOOKS_UPDATE(data: DispatchEventMap["WEBHOOKS_UPDATE"], shard: Shard): Promise { + shard.client.emit("webhooksUpdate", shard.client.guilds.get(data.guild_id) ?? { id: data.guild_id }, shard.client.getChannel(data.channel_id) ?? { id: data.channel_id }); +} diff --git a/lib/index.ts b/lib/index.ts index 376a9e47..2c1bf9c8 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -24,6 +24,9 @@ export { default as Collection } from "./util/Collection"; export { default as ComponentInteraction } from "./structures/ComponentInteraction"; export { default as DiscordHTTPError } from "./rest/DiscordHTTPError"; export { default as DiscordRESTError } from "./rest/DiscordRESTError"; +export { default as Dispatcher } from "./gateway/Dispatcher"; +export type * from "./gateway/Dispatcher"; +export * as DefaultDispatchEvents from "./gateway/events"; export * from "./util/Errors"; export * as Errors from "./util/Errors"; export { default as Entitlement } from "./structures/Entitlement"; diff --git a/lib/types/gateway.d.ts b/lib/types/gateway.d.ts index 457b8eb9..01873e7c 100644 --- a/lib/types/gateway.d.ts +++ b/lib/types/gateway.d.ts @@ -132,6 +132,11 @@ interface GatewayOptions { * @defaultValue based on `firstShardID` & `lastShardID` */ shardIDs?: Array; + /** + * If the built-in dispatch handlers should be used. Disabling this will result in no dispatch packets being handled by the client. You must handle **everything** yourself. + * @defaultValue true + */ + useDefaultDispatchHandlers?: boolean; /** The options to pass to constructed websockets. */ ws?: WSClientOptions; } From abb142e97f45d98ee9231f5d058bc84e1be7c1de Mon Sep 17 00:00:00 2001 From: Donovan Daniels Date: Wed, 17 Jul 2024 15:52:37 -0500 Subject: [PATCH 2/5] do thing --- lib/gateway/ShardManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gateway/ShardManager.ts b/lib/gateway/ShardManager.ts index 45572aba..97d6bf82 100644 --- a/lib/gateway/ShardManager.ts +++ b/lib/gateway/ShardManager.ts @@ -101,7 +101,7 @@ export default class ShardManager extends Collection { bitmask = AllNonPrivilegedIntents; continue; } - this.client.emit("warn", `Unknown intent: ${intent}`); + client.emit("warn", `Unknown intent: ${intent}`); } } From 91c02ef99cfa2b0d14d7796233dc5539eee6e260 Mon Sep 17 00:00:00 2001 From: Donovan Daniels Date: Fri, 19 Jul 2024 20:17:18 -0500 Subject: [PATCH 3/5] Always register READY/RESUMED handlers --- lib/gateway/Dispatcher.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/gateway/Dispatcher.ts b/lib/gateway/Dispatcher.ts index f5a913dc..79855655 100644 --- a/lib/gateway/Dispatcher.ts +++ b/lib/gateway/Dispatcher.ts @@ -23,6 +23,9 @@ export default class Dispatcher { for (const [event, fn] of Object.entries(DefaultDispatchEvents)) { this.register(event as DispatchEvent, fn as DispatchFunction); } + } else { + this.register("READY", DefaultDispatchEvents.READY); + this.register("RESUMED", DefaultDispatchEvents.RESUMED); } } From fb2e341839f316aa789fbdb12037fe7c57e4f71b Mon Sep 17 00:00:00 2001 From: Donovan Daniels Date: Fri, 19 Jul 2024 20:20:17 -0500 Subject: [PATCH 4/5] Update lib/gateway/Shard.ts Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> --- lib/gateway/Shard.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/gateway/Shard.ts b/lib/gateway/Shard.ts index 4f9b49ee..36aca7a6 100644 --- a/lib/gateway/Shard.ts +++ b/lib/gateway/Shard.ts @@ -132,7 +132,6 @@ export default class Shard extends TypedEmitter { } private _ready(data: ReadyPacket["d"]): void { - this.connectAttempts = 0; this.reconnectInterval = 1000; this.connecting = false; From babbe7bc877f135774fb7f0f942630b68c872128 Mon Sep 17 00:00:00 2001 From: Donovan Daniels Date: Fri, 19 Jul 2024 20:24:27 -0500 Subject: [PATCH 5/5] change note --- lib/types/gateway.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/types/gateway.d.ts b/lib/types/gateway.d.ts index 01873e7c..a5ee5f85 100644 --- a/lib/types/gateway.d.ts +++ b/lib/types/gateway.d.ts @@ -133,7 +133,8 @@ interface GatewayOptions { */ shardIDs?: Array; /** - * If the built-in dispatch handlers should be used. Disabling this will result in no dispatch packets being handled by the client. You must handle **everything** yourself. + * If the built-in dispatch handlers should be used. Disabling this will result in no dispatch packets being handled by the client. + * Handlers for `READY` and `RESUMED` will always be registered. You must handle **everything else** manually. * @defaultValue true */ useDefaultDispatchHandlers?: boolean;