gateway: initial implementation
vi did:web:vt3e.cat
Wed, 08 Jul 2026 07:00:00 +0100
5 files changed,
808 insertions(+),
11 deletions(-)
M
pkgs/gateway/README.md
→
pkgs/gateway/README.md
@@ -1,15 +1,26 @@
# @purrkit/gateway -To install dependencies: +```ts +import { Gateway } from "@purrkit/gateway"; + +const Intents = { + GUILDS: 1 << 0, + GUILD_MESSAGES: 1 << 9, + MESSAGE_CONTENT: 1 << 15, +}; +const intents = Intents.GUILDS | Intents.GUILD_MESSAGES | Intents.MESSAGE_CONTENT; +const gateway = Gateway({ + token: "...", + intents: intents, +}); -```bash -bun install -``` +gateway.on("READY", (data) => { + console.log(`logged in: ${data.user.username}`); // data: GatewayReadyDispatchData +}); -To run: +gateway.once("MESSAGE_CREATE", (message) => { + console.log(`first message caught: ${message.content}`); // message: MessageResponse +}); -```bash -bun run src/index.ts +gateway.connect(); ``` - -This project was created using `bun init` in bun v1.3.13. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
M
pkgs/gateway/package.json
→
pkgs/gateway/package.json
@@ -1,6 +1,6 @@
{ "name": "@purrkit/gateway", - "version": "1.0.0", + "version": "1.1.0", "license": "EUPL-1.2", "files": [ "dist",
A
pkgs/gateway/src/client.ts
@@ -0,0 +1,339 @@
+import { EventEmitter } from "events"; +import { OpCodes, type GatewayEvents } from "./types"; + +export interface GatewayClientOptions { + token: string; + intents: number; + shard?: [number, number]; + gatewayUrl?: string; +} + +export interface GatewayClientInstance { + on<K extends keyof GatewayEvents>(event: K, callback: (data: GatewayEvents[K]) => void): void; + once<K extends keyof GatewayEvents>(event: K, callback: (data: GatewayEvents[K]) => void): void; + off<K extends keyof GatewayEvents>(event: K, callback: (data: GatewayEvents[K]) => void): void; + + presence: { + update: (payload: { + status: "online" | "dnd" | "idle" | "invisible"; + activities: Array<{ name: string; type: number }>; + }) => void; + }; + guilds: (guildId: string) => { + requestMembers: (payload: { query?: string; limit?: number; user_ids?: string[] }) => void; + }; + connect: () => void; + disconnect: () => void; +} + +export function Gateway(options: GatewayClientOptions): GatewayClientInstance { + const emitter = new EventEmitter(); + let ws: WebSocket | null = null; + const baseGatewayUrl = options.gatewayUrl ?? "wss://gateway.discord.gg/?v=10&encoding=json"; + + let sequenceNum: number | null = null; + let sessionId: string | null = null; + let resumeGatewayUrl: string | null = null; + + let heartbeatTimeoutId: any = null; + let heartbeatIntervalId: any = null; + let connectionTimeoutId: any = null; + let reconnectTimeoutId: any = null; + + let isHeartbeatAcked = true; + let isReconnecting = false; + const reconnectDelay = 5000; + + const send = (op: OpCodes, d: any) => { + if (ws && ws.readyState === 1) { + ws.send(JSON.stringify({ op, d })); + } else { + console.warn(`Attempted to send payload (OP ${op}) but WebSocket is not open.`); + } + }; + + const heartbeat = () => { + if (!isHeartbeatAcked) { + console.warn("Heartbeat ACK missed. Reconnecting..."); + handleReconnect(); + return; + } + + isHeartbeatAcked = false; + send(OpCodes.Heartbeat, sequenceNum); + }; + + const startHeartbeat = (intervalMs: number) => { + stopHeartbeat(); + isHeartbeatAcked = true; + const jitter = Math.random() * intervalMs; + + heartbeatTimeoutId = setTimeout(() => { + heartbeat(); + heartbeatIntervalId = setInterval(heartbeat, intervalMs); + }, jitter); + }; + + const stopHeartbeat = () => { + if (heartbeatTimeoutId) { + clearTimeout(heartbeatTimeoutId); + heartbeatTimeoutId = null; + } + if (heartbeatIntervalId) { + clearInterval(heartbeatIntervalId); + heartbeatIntervalId = null; + } + }; + + const startConnectionTimeout = () => { + stopConnectionTimeout(); + connectionTimeoutId = setTimeout(() => { + console.warn("Gateway HELLO timeout reached. Reconnecting..."); + handleReconnect(); + }, 15000); + }; + + const stopConnectionTimeout = () => { + if (connectionTimeoutId) { + clearTimeout(connectionTimeoutId); + connectionTimeoutId = null; + } + }; + + const identify = () => { + const osPlatform = typeof process !== "undefined" ? process.platform : "browser"; + send(OpCodes.Identify, { + token: options.token, + intents: options.intents, + properties: { + os: osPlatform, + browser: "@purrkit/gateway", + device: "@purrkit/gateway", + }, + shard: options.shard, + }); + }; + + const resume = () => { + send(OpCodes.Resume, { + token: options.token, + session_id: sessionId, + seq: sequenceNum, + }); + }; + + const connect = () => { + isReconnecting = false; + stopConnectionTimeout(); + + let url = baseGatewayUrl; + if (sessionId && resumeGatewayUrl) { + try { + const parsedUrl = new URL(resumeGatewayUrl); + parsedUrl.searchParams.set("v", "10"); + parsedUrl.searchParams.set("encoding", "json"); + url = parsedUrl.toString(); + } catch (error) { + console.error( + "Failed to parse resume gateway URL, falling back to base gateway URL:", + error, + ); + + sessionId = null; + sequenceNum = null; + } + } + + try { + ws = new WebSocket(url); + } catch (error) { + console.error("Failed to construct WebSocket:", error); + handleReconnect(); + return; + } + + startConnectionTimeout(); + + ws.onopen = () => { + // wait on HELLO + }; + + ws.onmessage = (event) => { + let payload; + try { + const rawData = typeof event.data === "string" ? event.data : event.data.toString(); + payload = JSON.parse(rawData); + } catch (e) { + console.error("Failed to parse gateway JSON payload:", e); + return; + } + + const { op, d, s, t } = payload; + + if (s !== null && s !== undefined) { + sequenceNum = s; + } + + switch (op) { + case OpCodes.Dispatch: + if (t === "READY") { + sessionId = d.session_id; + resumeGatewayUrl = d.resume_gateway_url; + } + emitter.emit(t, d); + break; + case OpCodes.Heartbeat: + send(OpCodes.Heartbeat, sequenceNum); + break; + case OpCodes.Reconnect: + handleReconnect(); + break; + case OpCodes.InvalidSession: { + const jitterDelay = Math.floor(Math.random() * 4000) + 1000; + if (d) { + handleReconnect(jitterDelay); + } else { + sequenceNum = null; + sessionId = null; + resumeGatewayUrl = null; + handleReconnect(jitterDelay); + } + break; + } + case OpCodes.Hello: + stopConnectionTimeout(); + startHeartbeat(d.heartbeat_interval); + if (sessionId && resumeGatewayUrl) { + resume(); + } else { + identify(); + } + break; + case OpCodes.HeartbeatAck: + isHeartbeatAcked = true; + break; + } + }; + + ws.onclose = (event) => { + stopHeartbeat(); + stopConnectionTimeout(); + + const fatalCodes = [4004, 4010, 4011, 4012, 4013, 4014]; + if (fatalCodes.includes(event.code)) { + console.error( + `Gateway connection closed with fatal code: ${event.code}. Reconnection aborted.`, + ); + emitter.emit("fatal_disconnect", event.code); + return; + } + + if (event.code === 4007 || event.code === 4009) { + sequenceNum = null; + sessionId = null; + resumeGatewayUrl = null; + } + + handleReconnect(); + }; + + ws.onerror = (error) => { + console.error("Gateway WebSocket error:", error); + }; + }; + + const handleReconnect = (delay = reconnectDelay) => { + if (isReconnecting) return; + isReconnecting = true; + stopHeartbeat(); + stopConnectionTimeout(); + + if (reconnectTimeoutId) { + clearTimeout(reconnectTimeoutId); + reconnectTimeoutId = null; + } + + if (ws) { + ws.onopen = null; + ws.onmessage = null; + ws.onclose = null; + ws.onerror = null; + try { + ws.close(); + } catch {} + ws = null; + } + + reconnectTimeoutId = setTimeout(() => connect(), delay); + }; + + const disconnect = () => { + stopHeartbeat(); + stopConnectionTimeout(); + sessionId = null; + resumeGatewayUrl = null; + sequenceNum = null; + + if (reconnectTimeoutId) { + clearTimeout(reconnectTimeoutId); + reconnectTimeoutId = null; + } + + if (ws) { + ws.onopen = null; + ws.onmessage = null; + ws.onclose = null; + ws.onerror = null; + ws.close(1000, "Clean close requested"); + ws = null; + } + }; + + const on = <K extends keyof GatewayEvents>( + event: K, + callback: (data: GatewayEvents[K]) => void, + ) => { + emitter.on(event, callback); + }; + + const once = <K extends keyof GatewayEvents>( + event: K, + callback: (data: GatewayEvents[K]) => void, + ) => { + emitter.once(event, callback); + }; + + const off = <K extends keyof GatewayEvents>( + event: K, + callback: (data: GatewayEvents[K]) => void, + ) => { + emitter.off(event, callback); + }; + + const presence = { + update(payload: any) { + send(OpCodes.PresenceUpdate, payload); + }, + }; + + const guilds = (guildId: string) => { + return { + requestMembers(payload: any) { + send(OpCodes.RequestGuildMemberResponses, { + guild_id: guildId, + ...payload, + }); + }, + }; + }; + + return { + on, + once, + off, + presence, + guilds, + connect, + disconnect, + }; +}
M
pkgs/gateway/src/index.ts
→
pkgs/gateway/src/index.ts
@@ -1,1 +1,2 @@
-console.log("Hello via Bun!");+export * from "./types"; +export * from "./client";
A
pkgs/gateway/src/types.ts
@@ -0,0 +1,446 @@
+import type { + UserResponse, + GuildResponse, + GuildChannelResponse, + ThreadMemberResponse, + GuildMemberResponse, + EmojiResponse, + GuildStickerResponse, + AuditLogEntryResponse, + EntitlementResponse, + VoiceStateResponse, + StageInstanceResponse, + MessageResponse, + InteractionResponse, + DiscordIntegrationResponse, + ScheduledEventResponse, + GuildRoleResponse, + Snowflake, +} from "@purrkit/types"; + +export enum OpCodes { + /** Fired periodically by the client to keep the connection alive. */ + Dispatch = 0, + /** Starts a new session during the initial handshake. */ + Heartbeat = 1, + /** Starts a new session during the initial handshake. */ + Identify = 2, + /** Update the client’s presence. */ + PresenceUpdate = 3, + /** Used to join/leave or move between voice channels. */ + VoiceStateUpdate = 4, + /** Resume a previous session that was disconnected. */ + Resume = 6, + /** You should attempt to reconnect and resume immediately. */ + Reconnect = 7, + /** Request information about offline GuildResponse members in a large GuildResponse. */ + RequestGuildMemberResponses = 8, + /** The session has been invalidated. You should reconnect and identify/resume accordingly. */ + InvalidSession = 9, + /** Sent immediately after connecting, contains the heartbeat_interval to use. */ + Hello = 10, + /** Sent in response to receiving a heartbeat to acknowledge that it has been received. */ + HeartbeatAck = 11, + + /** Request information about soundboard sounds in a set of guilds. */ + RequestSoundboardSounds = 31, + + /** Request ephemeral channel data for channels in a GuildResponse. */ + RequestChannelInfo = 43, +} + +export type APIGuildUnavailable = { + id: Snowflake; + unavailable: boolean; +}; + +export interface GatewayEvents { + READY: GatewayReadyDispatchData; + RESUMED: Record<string, never>; + APPLICATION_COMMAND_PERMISSIONS_UPDATE: GatewayApplicationCommandPermissionsUpdateDispatchData; + AUTO_MODERATION_RULE_CREATE: GatewayAutoModerationRuleCreateDispatchData; + AUTO_MODERATION_RULE_UPDATE: GatewayAutoModerationRuleUpdateDispatchData; + AUTO_MODERATION_RULE_DELETE: GatewayAutoModerationRuleDeleteDispatchData; + AUTO_MODERATION_ACTION_EXECUTION: GatewayAutoModerationActionExecutionDispatchData; + CHANNEL_CREATE: GatewayChannelCreateDispatchData; + CHANNEL_UPDATE: GatewayChannelUpdateDispatchData; + CHANNEL_DELETE: GatewayChannelDeleteDispatchData; + CHANNEL_PINS_UPDATE: GatewayChannelPinsUpdateDispatchData; + THREAD_CREATE: GatewayThreadCreateDispatchData; + THREAD_UPDATE: GatewayThreadUpdateDispatchData; + THREAD_DELETE: GatewayThreadDeleteDispatchData; + THREAD_LIST_SYNC: GatewayThreadListSyncDispatchData; + THREAD_MEMBER_UPDATE: GatewayThreadMemberUpdateDispatchData; + THREAD_MEMBERS_UPDATE: GatewayThreadMembersUpdateDispatchData; + GUILD_CREATE: GatewayGuildCreateDispatchData; + GUILD_UPDATE: GatewayGuildUpdateDispatchData; + GUILD_DELETE: GatewayGuildDeleteDispatchData; + GUILD_AUDIT_LOG_ENTRY_CREATE: GatewayGuildAuditLogEntryCreateDispatchData; + GUILD_BAN_ADD: GatewayGuildBanAddDispatchData; + GUILD_BAN_REMOVE: GatewayGuildBanRemoveDispatchData; + GUILD_EMOJIS_UPDATE: GatewayGuildEmojisUpdateDispatchData; + GUILD_STICKERS_UPDATE: GatewayGuildStickersUpdateDispatchData; + GUILD_INTEGRATIONS_UPDATE: GatewayGuildIntegrationsUpdateDispatchData; + GUILD_MEMBER_ADD: GatewayGuildMemberResponseAddDispatchData; + GUILD_MEMBER_REMOVE: GatewayGuildMemberResponseRemoveDispatchData; + GUILD_MEMBER_UPDATE: GatewayGuildMemberResponseUpdateDispatchData; + GUILD_MEMBERS_CHUNK: GatewayGuildMemberResponsesChunkDispatchData; + GUILD_ROLE_CREATE: GatewayGuildRoleCreateDispatchData; + GUILD_ROLE_UPDATE: GatewayGuildRoleUpdateDispatchData; + GUILD_ROLE_DELETE: GatewayGuildRoleDeleteDispatchData; + GUILD_SCHEDULED_EVENT_CREATE: GatewayGuildScheduledEventCreateDispatchData; + GUILD_SCHEDULED_EVENT_UPDATE: GatewayGuildScheduledEventUpdateDispatchData; + GUILD_SCHEDULED_EVENT_DELETE: GatewayGuildScheduledEventDeleteDispatchData; + GUILD_SCHEDULED_EVENT_USER_ADD: GatewayGuildScheduledEventUserAddDispatchData; + GUILD_SCHEDULED_EVENT_USER_REMOVE: GatewayGuildScheduledEventUserRemoveDispatchData; + INTEGRATION_CREATE: GatewayIntegrationCreateDispatchData; + INTEGRATION_UPDATE: GatewayIntegrationUpdateDispatchData; + INTEGRATION_DELETE: GatewayIntegrationDeleteDispatchData; + INTERACTION_CREATE: GatewayInteractionCreateDispatchData; + INVITE_CREATE: GatewayInviteCreateDispatchData; + INVITE_DELETE: GatewayInviteDeleteDispatchData; + MESSAGE_CREATE: GatewayMessageCreateDispatchData; + MESSAGE_UPDATE: GatewayMessageUpdateDispatchData; + MESSAGE_DELETE: GatewayMessageDeleteDispatchData; + MESSAGE_DELETE_BULK: GatewayMessageDeleteBulkDispatchData; + MESSAGE_REACTION_ADD: GatewayMessageReactionAddDispatchData; + MESSAGE_REACTION_REMOVE: GatewayMessageReactionRemoveDispatchData; + MESSAGE_REACTION_REMOVE_ALL: GatewayMessageReactionRemoveAllDispatchData; + MESSAGE_REACTION_REMOVE_EMOJI: GatewayMessageReactionRemoveEmojiDispatchData; + PRESENCE_UPDATE: GatewayPresenceUpdateDispatchData; + STAGE_INSTANCE_CREATE: GatewayStageInstanceCreateDispatchData; + STAGE_INSTANCE_UPDATE: GatewayStageInstanceUpdateDispatchData; + STAGE_INSTANCE_DELETE: GatewayStageInstanceDeleteDispatchData; + TYPING_START: GatewayTypingStartDispatchData; + USER_UPDATE: GatewayUserUpdateDispatchData; + VOICE_STATE_UPDATE: GatewayVoiceStateUpdateDispatchData; + VOICE_SERVER_UPDATE: GatewayVoiceServerUpdateDispatchData; + WEBHOOKS_UPDATE: GatewayWebhooksUpdateDispatchData; + ENTITLEMENT_CREATE: GatewayEntitlementCreateDispatchData; + ENTITLEMENT_UPDATE: GatewayEntitlementUpdateDispatchData; + ENTITLEMENT_DELETE: GatewayEntitlementDeleteDispatchData; +} + +export interface GatewayReadyDispatchData { + v: number; + user: UserResponse; + guilds: APIGuildUnavailable[]; + session_id: string; + resume_gateway_url: string; + shard?: [shard_id: number, num_shards: number]; + application: { id: Snowflake; flags: number }; +} + +export interface GatewayApplicationCommandPermissionsUpdateDispatchData { + id: Snowflake; + application_id: Snowflake; + guild_id: Snowflake; + permissions: { + id: Snowflake; + type: number; // 1 = ROLE, 2 = USER, 3 = CHANNEL + permission: boolean; + }[]; +} + +// TOOD)) types for below +export type GatewayAutoModerationRuleCreateDispatchData = unknown; +export type GatewayAutoModerationRuleUpdateDispatchData = unknown; +export type GatewayAutoModerationRuleDeleteDispatchData = unknown; + +export interface GatewayAutoModerationActionExecutionDispatchData { + guild_id: Snowflake; + action: { + type: number; + metadata?: any; + }; + rule_id: Snowflake; + rule_trigger_type: number; + user_id: Snowflake; + channel_id?: Snowflake; + message_id?: Snowflake; + alert_system_message_id?: Snowflake; + content: string; + matched_keyword: string | null; + matched_content: string | null; +} + +export type GatewayChannelCreateDispatchData = GuildChannelResponse; +export type GatewayChannelUpdateDispatchData = GuildChannelResponse; +export type GatewayChannelDeleteDispatchData = GuildChannelResponse; + +export interface GatewayChannelPinsUpdateDispatchData { + guild_id?: Snowflake; + channel_id: Snowflake; + last_pin_timestamp?: string | null; +} + +export interface GatewayThreadCreateDispatchData extends GuildChannelResponse { + newly_created?: boolean; + thread_member?: ThreadMemberResponse; +} +export type GatewayThreadUpdateDispatchData = GuildChannelResponse; +export interface GatewayThreadDeleteDispatchData { + id: Snowflake; + guild_id: Snowflake; + parent_id: Snowflake; + type: number; +} + +export interface GatewayThreadListSyncDispatchData { + guild_id: Snowflake; + channel_ids?: Snowflake[]; + threads: GuildChannelResponse[]; + members: ThreadMemberResponse[]; +} + +export interface GatewayThreadMemberUpdateDispatchData extends ThreadMemberResponse { + guild_id: Snowflake; +} + +export interface GatewayThreadMembersUpdateDispatchData { + id: Snowflake; + guild_id: Snowflake; + member_count: number; + added_members?: ThreadMemberResponse[]; + removed_member_ids?: Snowflake[]; +} + +export interface GatewayGuildCreateDispatchData extends GuildResponse { + joined_at?: string; + large?: boolean; + unavailable?: boolean; + member_count?: number; + voice_states?: Omit<VoiceStateResponse, "guild_id">[]; + members?: GuildMemberResponse[]; + channels?: GuildChannelResponse[]; + threads?: GuildChannelResponse[]; + presences?: GatewayPresenceUpdateDispatchData[]; + stage_instances?: StageInstanceResponse[]; + guild_scheduled_events?: ScheduledEventResponse[]; +} + +export type GatewayGuildUpdateDispatchData = GuildResponse; + +export interface GatewayGuildDeleteDispatchData { + id: Snowflake; + unavailable?: boolean; +} + +export type GatewayGuildAuditLogEntryCreateDispatchData = AuditLogEntryResponse; + +export interface GatewayGuildBanAddDispatchData { + guild_id: Snowflake; + user: UserResponse; +} + +export interface GatewayGuildBanRemoveDispatchData { + guild_id: Snowflake; + user: UserResponse; +} + +export interface GatewayGuildEmojisUpdateDispatchData { + guild_id: Snowflake; + emojis: EmojiResponse[]; +} + +export interface GatewayGuildStickersUpdateDispatchData { + guild_id: Snowflake; + stickers: GuildStickerResponse[]; +} + +export interface GatewayGuildIntegrationsUpdateDispatchData { + guild_id: Snowflake; +} + +export interface GatewayGuildMemberResponseAddDispatchData extends GuildMemberResponse { + guild_id: Snowflake; +} + +export interface GatewayGuildMemberResponseRemoveDispatchData { + guild_id: Snowflake; + user: UserResponse; +} + +export interface GatewayGuildMemberResponseUpdateDispatchData { + guild_id: Snowflake; + roles: Snowflake[]; + user: UserResponse; + avatar: string | null; + joined_at: string | null; + premium_since?: string | null; + deaf?: boolean; + mute?: boolean; + pending?: boolean; + communication_disabled_until?: string | null; +} + +export interface GatewayGuildMemberResponsesChunkDispatchData { + guild_id: Snowflake; + members: GuildMemberResponse[]; + chunk_index: number; + chunk_count: number; + not_found?: Snowflake[]; + presences?: GatewayPresenceUpdateDispatchData[]; + nonce?: string; +} + +export interface GatewayGuildRoleCreateDispatchData { + guild_id: Snowflake; + role: GuildRoleResponse; +} + +export interface GatewayGuildRoleUpdateDispatchData { + guild_id: Snowflake; + role: GuildRoleResponse; +} + +export interface GatewayGuildRoleDeleteDispatchData { + guild_id: Snowflake; + role_id: Snowflake; +} + +export type GatewayGuildScheduledEventCreateDispatchData = ScheduledEventResponse; +export type GatewayGuildScheduledEventUpdateDispatchData = ScheduledEventResponse; +export type GatewayGuildScheduledEventDeleteDispatchData = ScheduledEventResponse; + +export interface GatewayGuildScheduledEventUserAddDispatchData { + guild_scheduled_event_id: Snowflake; + user_id: Snowflake; + guild_id: Snowflake; +} + +export interface GatewayGuildScheduledEventUserRemoveDispatchData { + guild_scheduled_event_id: Snowflake; + user_id: Snowflake; + guild_id: Snowflake; +} + +export interface GatewayIntegrationCreateDispatchData extends DiscordIntegrationResponse { + guild_id: Snowflake; +} + +export interface GatewayIntegrationUpdateDispatchData extends DiscordIntegrationResponse { + guild_id: Snowflake; +} + +export interface GatewayIntegrationDeleteDispatchData { + id: Snowflake; + guild_id: Snowflake; + application_id?: Snowflake; +} + +export type GatewayInteractionCreateDispatchData = InteractionResponse; + +export interface GatewayInviteCreateDispatchData { + channel_id: Snowflake; + code: string; + created_at: string; + guild_id?: Snowflake; + inviter?: UserResponse; + max_age: number; + max_uses: number; + temporary: boolean; + uses: number; + target_type?: number; + target_user?: UserResponse; + target_application?: any; +} + +export interface GatewayInviteDeleteDispatchData { + channel_id: Snowflake; + guild_id?: Snowflake; + code: string; +} + +export type GatewayMessageCreateDispatchData = MessageResponse; + +export interface GatewayMessageUpdateDispatchData extends Partial<MessageResponse> { + id: Snowflake; + channel_id: Snowflake; +} + +export interface GatewayMessageDeleteDispatchData { + id: Snowflake; + channel_id: Snowflake; + guild_id?: Snowflake; +} + +export interface GatewayMessageDeleteBulkDispatchData { + ids: Snowflake[]; + channel_id: Snowflake; + guild_id?: Snowflake; +} + +export interface GatewayMessageReactionAddDispatchData { + user_id: Snowflake; + channel_id: Snowflake; + message_id: Snowflake; + guild_id?: Snowflake; + member?: GuildMemberResponse; + emoji: EmojiResponse; + message_author_id?: Snowflake; +} + +export interface GatewayMessageReactionRemoveDispatchData { + user_id: Snowflake; + channel_id: Snowflake; + message_id: Snowflake; + guild_id?: Snowflake; + emoji: EmojiResponse; +} + +export interface GatewayMessageReactionRemoveAllDispatchData { + channel_id: Snowflake; + message_id: Snowflake; + guild_id?: Snowflake; +} + +export interface GatewayMessageReactionRemoveEmojiDispatchData { + channel_id: Snowflake; + message_id: Snowflake; + guild_id?: Snowflake; + emoji: EmojiResponse; +} + +export interface GatewayPresenceUpdateDispatchData { + user: Partial<UserResponse> & { id: Snowflake }; + guild_id?: Snowflake; + status: "online" | "dnd" | "idle" | "invisible" | "offline"; + activities: any[]; + client_status: { + desktop?: string; + mobile?: string; + web?: string; + }; +} + +export type GatewayStageInstanceCreateDispatchData = StageInstanceResponse; +export type GatewayStageInstanceUpdateDispatchData = StageInstanceResponse; +export type GatewayStageInstanceDeleteDispatchData = StageInstanceResponse; + +export interface GatewayTypingStartDispatchData { + channel_id: Snowflake; + guild_id?: Snowflake; + user_id: Snowflake; + timestamp: number; + member?: GuildMemberResponse; +} + +export type GatewayUserUpdateDispatchData = UserResponse; + +export type GatewayVoiceStateUpdateDispatchData = VoiceStateResponse; + +export interface GatewayVoiceServerUpdateDispatchData { + token: string; + guild_id: Snowflake; + endpoint: string | null; +} + +export interface GatewayWebhooksUpdateDispatchData { + guild_id: Snowflake; + channel_id: Snowflake; +} + +export type GatewayEntitlementCreateDispatchData = EntitlementResponse; +export type GatewayEntitlementUpdateDispatchData = EntitlementResponse; +export type GatewayEntitlementDeleteDispatchData = EntitlementResponse;