lunibee 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -24
- package/dist/builders/commands.d.ts +37 -0
- package/dist/builders/components.d.ts +127 -12
- package/dist/builders/embed.d.ts +3 -1
- package/dist/builders/index.d.ts +2 -2
- package/dist/builders/index.js +319 -16
- package/dist/builders/index.js.map +8 -7
- package/dist/collection/index.d.ts +36 -0
- package/dist/collection/index.js +110 -6
- package/dist/collection/index.js.map +4 -4
- package/dist/core/events.d.ts +69 -3
- package/dist/core/index.d.ts +158 -90
- package/dist/core/index.js +1543 -1169
- package/dist/core/index.js.map +24 -23
- package/dist/core/permissions.d.ts +112 -53
- package/dist/formatters/index.d.ts +43 -3
- package/dist/formatters/index.js +64 -1
- package/dist/formatters/index.js.map +3 -3
- package/dist/handlers/index.js.map +2 -2
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3882 -1588
- package/dist/index.js.map +43 -35
- package/dist/managers/emoji.d.ts +38 -0
- package/dist/managers/guild.d.ts +141 -0
- package/dist/managers/index.d.ts +86 -75
- package/dist/managers/index.js +1084 -67
- package/dist/managers/index.js.map +21 -16
- package/dist/managers/message.d.ts +7 -12
- package/dist/managers/user.d.ts +20 -0
- package/dist/rest/index.d.ts +173 -1
- package/dist/rest/index.js +343 -47
- package/dist/rest/index.js.map +8 -6
- package/dist/rest/redis.d.ts +37 -0
- package/dist/rest/routes.d.ts +26 -1
- package/dist/rest/store.d.ts +38 -0
- package/dist/rest/webhook.d.ts +2 -2
- package/dist/sharding/cluster.d.ts +61 -0
- package/dist/sharding/index.d.ts +8 -0
- package/dist/sharding/index.js +289 -18
- package/dist/sharding/index.js.map +8 -7
- package/dist/structures/audit-log.d.ts +26 -0
- package/dist/structures/base.d.ts +131 -48
- package/dist/structures/embed.d.ts +65 -0
- package/dist/structures/index.d.ts +74 -26
- package/dist/structures/index.js +1528 -59
- package/dist/structures/index.js.map +14 -8
- package/dist/structures/interactions.d.ts +72 -17
- package/dist/structures/resources.d.ts +199 -7
- package/dist/types/index.d.ts +605 -84
- package/dist/types/index.js +71 -1
- package/dist/types/index.js.map +3 -3
- package/dist/utils/index.js.map +2 -2
- package/dist/voice/index.d.ts +88 -0
- package/dist/voice/index.js +204 -5
- package/dist/voice/index.js.map +3 -3
- package/dist/ws/index.d.ts +40 -1
- package/dist/ws/index.js +105 -18
- package/dist/ws/index.js.map +4 -4
- package/package.json +16 -16
- package/dist/structures/permissions.d.ts +0 -73
package/dist/voice/index.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../packages/voice/src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/** Voice session lifecycle states. */\r\nexport enum VoiceConnectionState {\r\n Disconnected = \"disconnected\",\r\n Connecting = \"connecting\",\r\n Connected = \"connected\",\r\n Destroyed = \"destroyed\",\r\n}\r\n\r\n/** Voice protocol transport errors. */\r\nexport class VoiceError extends Error {\r\n /** Creates a voice error. @param message Error message. @param options Optional native error options. */\r\n public constructor(message: string, options?: ErrorOptions) {\r\n super(message, options);\r\n this.name = \"VoiceError\";\r\n }\r\n}\r\n\r\n/** Options used to establish a voice session. */\r\nexport interface VoiceConnectionOptions {\r\n /** Guild containing the voice channel. */\r\n guildId: string;\r\n /** Voice channel to join. */\r\n channelId?: string;\r\n /** Whether the self user should be muted. */\r\n selfMute?: boolean;\r\n /** Whether the self user should be deafened. */\r\n selfDeaf?: boolean;\r\n}\r\n\r\n/** UDP transport supplied by the runtime-specific voice implementation. */\r\nexport interface VoiceUdpTransport {\r\n /** Sends an RTP/RTCP packet. @param packet Packet bytes. @param host Destination host. @param port Destination port. @returns Optional completion promise. */\r\n send(packet: Uint8Array, host: string, port: number): Promise<void> | void;\r\n /** Closes the UDP socket. @returns Nothing. */\r\n close(): void;\r\n}\r\n\r\n/** Voice Gateway transport abstraction. */\r\nexport interface VoiceGatewayTransport {\r\n /** Opens the voice gateway. @param endpoint Voice gateway endpoint. @param sessionId Voice session identifier. @returns Promise fulfilled when connected. */\r\n connect(endpoint: string, sessionId: string): Promise<void>;\r\n /** Sends a voice gateway payload. @param payload Gateway payload. @returns Nothing. */\r\n send(payload: unknown): void;\r\n /** Closes the voice gateway. @param code Optional close code. @param reason Optional close reason. @returns Nothing. */\r\n close(code?: number, reason?: string): void;\r\n}\r\n\r\n/** Speaking flags used by the voice gateway. */\r\nexport const SpeakingFlags = {\r\n Microphone: 1,\r\n Soundshare: 2,\r\n Priority: 4,\r\n} as const;\r\n\r\n/** Voice session event payloads. */\r\nexport interface VoiceEvents {\r\n /** State transition event. */\r\n stateChange: [VoiceConnectionState, VoiceConnectionState];\r\n /** Speaking flag event. */\r\n speaking: [number, number];\r\n /** Voice transport error event. */\r\n error: [VoiceError];\r\n}\r\n\r\ntype VoiceListener = (...args: any[]) => unknown;\r\n\r\n/** Tracks a Discord voice session and exposes transport-independent lifecycle controls. */\r\nexport class VoiceConnection {\r\n /** Current voice connection state. */\r\n public state: VoiceConnectionState = VoiceConnectionState.Disconnected;\r\n /** Guild associated with this voice session. */\r\n public readonly guildId: string;\r\n /** Voice channel associated with this session. */\r\n public channelId?: string;\r\n /** Whether the client is self-muted. */\r\n public selfMute: boolean;\r\n /** Whether the client is self-deafened. */\r\n public selfDeaf: boolean;\r\n /** Active voice gateway transport, when attached. */\r\n public gateway?: VoiceGatewayTransport;\r\n /** Active UDP transport, when attached. */\r\n public udp?: VoiceUdpTransport;\r\n /** Registered lifecycle listeners. */\r\n readonly #listeners = new Map<keyof VoiceEvents, Set<VoiceListener>>();\r\n\r\n /** Creates a voice connection state manager. @param options Guild ID or connection options. @throws {TypeError} If the guild ID is empty. */\r\n public constructor(options: string | VoiceConnectionOptions) {\r\n const resolved =\r\n typeof options === \"string\" ? { guildId: options } : options;\r\n if (!resolved.guildId?.trim()) {\r\n throw new TypeError(\"A guild ID is required for a voice connection.\");\r\n }\r\n this.guildId = resolved.guildId;\r\n this.channelId = resolved.channelId;\r\n this.selfMute = resolved.selfMute ?? false;\r\n this.selfDeaf = resolved.selfDeaf ?? false;\r\n }\r\n\r\n /** Registers a voice lifecycle listener. @param event Event name. @param listener Listener callback. @returns This connection. @throws {TypeError} If listener is not callable. */\r\n public on<K extends keyof VoiceEvents>(\r\n event: K,\r\n listener: (...args: VoiceEvents[K]) => unknown,\r\n ): this {\r\n if (typeof listener !== \"function\")\r\n throw new TypeError(\"Voice listener must be a function.\");\r\n let listeners = this.#listeners.get(event);\r\n if (!listeners) this.#listeners.set(event, (listeners = new Set()));\r\n listeners.add(listener);\r\n return this;\r\n }\r\n\r\n /** Removes a voice lifecycle listener. @param event Event name. @param listener Listener callback. @returns This connection. */\r\n public off<K extends keyof VoiceEvents>(\r\n event: K,\r\n listener: (...args: VoiceEvents[K]) => unknown,\r\n ): this {\r\n this.#listeners.get(event)?.delete(listener);\r\n return this;\r\n }\r\n\r\n /** Attaches protocol transports, replacing and cleaning up the old pair when present. @param gateway Voice gateway transport. @param udp UDP transport. @returns This connection. @throws {VoiceError} If the connection is destroyed. @throws {TypeError} If a transport is missing. */\r\n public attachTransports(\r\n gateway: VoiceGatewayTransport,\r\n udp: VoiceUdpTransport,\r\n ): this {\r\n this.assertUsable();\r\n if (!gateway || !udp)\r\n throw new TypeError(\r\n \"Both voice gateway and UDP transports are required.\",\r\n );\r\n if (this.gateway || this.udp) this.#closeTransports();\r\n this.gateway = gateway;\r\n this.udp = udp;\r\n return this;\r\n }\r\n\r\n /** Begins the connection lifecycle. @returns Nothing. @throws {VoiceError} If the connection is destroyed. */\r\n public connect(): void {\r\n this.assertUsable();\r\n if (this.state === VoiceConnectionState.Connected) return;\r\n this.#transition(VoiceConnectionState.Connecting);\r\n if (this.state !== VoiceConnectionState.Connecting) return;\r\n this.#transition(VoiceConnectionState.Connected);\r\n }\r\n\r\n /** Disconnects the current voice session. @returns Nothing. */\r\n public disconnect(): void {\r\n if (this.state === VoiceConnectionState.Destroyed) return;\r\n this.#closeTransports();\r\n this.#transition(VoiceConnectionState.Disconnected);\r\n }\r\n\r\n /** Permanently destroys this voice connection. @returns Nothing. */\r\n public destroy(): void {\r\n if (this.state === VoiceConnectionState.Destroyed) return;\r\n this.#closeTransports();\r\n this.channelId = undefined;\r\n this.#transition(VoiceConnectionState.Destroyed);\r\n this.#listeners.clear();\r\n }\r\n\r\n /** Updates the voice channel and connection state. @param channelId Voice channel identifier. @returns Nothing. @throws {VoiceError} If the connection is destroyed. @throws {TypeError} If channelId is empty. */\r\n public setChannel(channelId: string): void {\r\n this.assertUsable();\r\n if (!channelId?.trim())\r\n throw new TypeError(\"A voice channel ID is required.\");\r\n this.channelId = channelId;\r\n }\r\n\r\n /** Updates self mute/deaf state. @param options Suppression flags. @returns Nothing. @throws {VoiceError} If the connection is destroyed. */\r\n public setSuppression(options: {\r\n selfMute?: boolean;\r\n selfDeaf?: boolean;\r\n }): void {\r\n this.assertUsable();\r\n if (options.selfMute !== undefined) this.selfMute = options.selfMute;\r\n if (options.selfDeaf !== undefined) this.selfDeaf = options.selfDeaf;\r\n }\r\n\r\n /** Publishes a speaking state through the attached voice gateway. @param flags Speaking flags. @returns Nothing. @throws {VoiceError} If the connection is destroyed or has no gateway transport. */\r\n public setSpeaking(flags: number = SpeakingFlags.Microphone): void {\r\n this.assertUsable();\r\n if (!this.gateway) {\r\n throw new VoiceError(\r\n \"A voice gateway transport is required to change speaking state.\",\r\n );\r\n }\r\n this.gateway.send({ op: 5, d: { speaking: flags, delay: 0, ssrc: 0 } });\r\n this.#emit(\"speaking\", flags, 0);\r\n }\r\n\r\n /** Ensures the connection is still usable. @returns Nothing. @throws {VoiceError} If destroyed. */\r\n #assertUsable(): void {\r\n if (this.state === VoiceConnectionState.Destroyed) {\r\n throw new VoiceError(\"Voice connection has been destroyed.\");\r\n }\r\n }\r\n\r\n /** Closes both active transports while isolating cleanup failures. @returns Nothing. */\r\n #closeTransports(): void {\r\n try {\r\n this.gateway?.close(1000, \"Voice connection replaced or closed\");\r\n } catch (error) {\r\n this.#emit(\r\n \"error\",\r\n error instanceof VoiceError\r\n ? error\r\n : new VoiceError(\"Voice gateway cleanup failed.\", { cause: error }),\r\n );\r\n }\r\n try {\r\n this.udp?.close();\r\n } catch (error) {\r\n this.#emit(\r\n \"error\",\r\n error instanceof VoiceError\r\n ? error\r\n : new VoiceError(\"Voice UDP cleanup failed.\", { cause: error }),\r\n );\r\n }\r\n this.gateway = undefined;\r\n this.udp = undefined;\r\n }\r\n\r\n /** Transitions to a new lifecycle state and emits the transition event. @param next Next state. @returns Nothing. */\r\n #transition(next: VoiceConnectionState): void {\r\n const previous = this.state;\r\n this.state = next;\r\n if (previous === next) return;\r\n for (const listener of this.#listeners.get(\"stateChange\") ?? []) {\r\n try {\r\n void listener(next, previous);\r\n } catch (error) {\r\n this.#emit(\r\n \"error\",\r\n error instanceof VoiceError\r\n ? error\r\n : new VoiceError(\"Voice event listener failed.\", { cause: error }),\r\n );\r\n }\r\n if (this.state !== next && next !== VoiceConnectionState.Destroyed)\r\n return;\r\n }\r\n }\r\n\r\n /** Emits a voice event while isolating consumer failures. @param event Event name. @param args Event arguments. @returns Nothing. */\r\n #emit<K extends keyof VoiceEvents>(event: K, ...args: VoiceEvents[K]): void {\r\n for (const listener of this.#listeners.get(event) ?? []) {\r\n try {\r\n void listener(...args);\r\n } catch {\r\n /* Consumer failures are isolated. */\r\n }\r\n }\r\n }\r\n\r\n /** Ensures the connection is still usable. @returns Nothing. @throws {VoiceError} If destroyed. */\r\n private assertUsable(): void {\r\n this.#assertUsable();\r\n }\r\n}\r\n"
|
|
5
|
+
"/** Voice session lifecycle states. */\r\nexport enum VoiceConnectionState {\r\n Disconnected = \"disconnected\",\r\n Connecting = \"connecting\",\r\n Connected = \"connected\",\r\n Destroyed = \"destroyed\",\r\n}\r\n\r\n/** Voice protocol transport errors. */\r\nexport class VoiceError extends Error {\r\n /** Creates a voice error. @param message Error message. @param options Optional native error options. */\r\n public constructor(message: string, options?: ErrorOptions) {\r\n super(message, options);\r\n this.name = \"VoiceError\";\r\n }\r\n}\r\n\r\n/** Options used to establish a voice session. */\r\nexport interface VoiceConnectionOptions {\r\n /** Guild containing the voice channel. */\r\n guildId: string;\r\n /** Voice channel to join. */\r\n channelId?: string;\r\n /** Whether the self user should be muted. */\r\n selfMute?: boolean;\r\n /** Whether the self user should be deafened. */\r\n selfDeaf?: boolean;\r\n}\r\n\r\n/** UDP transport supplied by the runtime-specific voice implementation. */\r\nexport interface VoiceUdpTransport {\r\n /** Sends an RTP/RTCP packet. @param packet Packet bytes. @param host Destination host. @param port Destination port. @returns Optional completion promise. */\r\n send(packet: Uint8Array, host: string, port: number): Promise<void> | void;\r\n /** Closes the UDP socket. @returns Nothing. */\r\n close(): void;\r\n /** Optional listener for incoming UDP packets. */\r\n onMessage?(listener: (packet: Uint8Array) => void): void;\r\n}\r\n\r\n/** Voice Gateway transport abstraction. */\r\nexport interface VoiceGatewayTransport {\r\n /** Opens the voice gateway. @param endpoint Voice gateway endpoint. @param sessionId Voice session identifier. @returns Promise fulfilled when connected. */\r\n connect(endpoint: string, sessionId: string): Promise<void>;\r\n /** Sends a voice gateway payload. @param payload Gateway payload. @returns Nothing. */\r\n send(payload: unknown): void;\r\n /** Closes the voice gateway. @param code Optional close code. @param reason Optional close reason. @returns Nothing. */\r\n close(code?: number, reason?: string): void;\r\n}\r\n\r\n/** Speaking flags used by the voice gateway. */\r\nexport const SpeakingFlags = {\r\n Microphone: 1,\r\n Soundshare: 2,\r\n Priority: 4,\r\n} as const;\r\n\r\n/** Voice session event payloads. */\r\nexport interface VoiceEvents {\r\n /** State transition event. */\r\n stateChange: [VoiceConnectionState, VoiceConnectionState];\r\n /** Speaking flag event. */\r\n speaking: [number, number];\r\n /** Voice transport error event. */\r\n error: [VoiceError];\r\n}\r\n\r\ntype VoiceListener = (...args: any[]) => unknown;\r\n\r\n/** Tracks a Discord voice session and exposes transport-independent lifecycle controls. */\r\nexport class VoiceConnection {\r\n /** Current voice connection state. */\r\n public state: VoiceConnectionState = VoiceConnectionState.Disconnected;\r\n /** Guild associated with this voice session. */\r\n public readonly guildId: string;\r\n /** Voice channel associated with this session. */\r\n public channelId?: string;\r\n /** Whether the client is self-muted. */\r\n public selfMute: boolean;\r\n /** Whether the client is self-deafened. */\r\n public selfDeaf: boolean;\r\n /** Active voice gateway transport, when attached. */\r\n public gateway?: VoiceGatewayTransport;\r\n /** Active UDP transport, when attached. */\r\n public udp?: VoiceUdpTransport;\r\n /** Registered lifecycle listeners. */\r\n readonly #listeners = new Map<keyof VoiceEvents, Set<VoiceListener>>();\r\n /** Receives incoming audio. */\r\n public readonly receiver: VoiceReceiver;\r\n\r\n /** Creates a voice connection state manager. @param options Guild ID or connection options. @throws {TypeError} If the guild ID is empty. */\r\n public constructor(options: string | VoiceConnectionOptions) {\r\n const resolved =\r\n typeof options === \"string\" ? { guildId: options } : options;\r\n if (!resolved.guildId?.trim()) {\r\n throw new TypeError(\r\n \"A guild ID is required for a voice connection.\",\r\n );\r\n }\r\n this.guildId = resolved.guildId;\r\n this.channelId = resolved.channelId;\r\n this.selfMute = resolved.selfMute ?? false;\r\n this.selfDeaf = resolved.selfDeaf ?? false;\r\n this.receiver = new VoiceReceiver(this);\r\n }\r\n\r\n /** Registers a voice lifecycle listener. @param event Event name. @param listener Listener callback. @returns This connection. @throws {TypeError} If listener is not callable. */\r\n public on<K extends keyof VoiceEvents>(\r\n event: K,\r\n listener: (...args: VoiceEvents[K]) => unknown,\r\n ): this {\r\n if (typeof listener !== \"function\")\r\n throw new TypeError(\"Voice listener must be a function.\");\r\n let listeners = this.#listeners.get(event);\r\n if (!listeners) this.#listeners.set(event, (listeners = new Set()));\r\n listeners.add(listener);\r\n return this;\r\n }\r\n\r\n /** Removes a voice lifecycle listener. @param event Event name. @param listener Listener callback. @returns This connection. */\r\n public off<K extends keyof VoiceEvents>(\r\n event: K,\r\n listener: (...args: VoiceEvents[K]) => unknown,\r\n ): this {\r\n this.#listeners.get(event)?.delete(listener);\r\n return this;\r\n }\r\n\r\n /** Attaches protocol transports, replacing and cleaning up the old pair when present. @param gateway Voice gateway transport. @param udp UDP transport. @returns This connection. @throws {VoiceError} If the connection is destroyed. @throws {TypeError} If a transport is missing. */\r\n public attachTransports(\r\n gateway: VoiceGatewayTransport,\r\n udp: VoiceUdpTransport,\r\n ): this {\r\n this.assertUsable();\r\n if (!gateway || !udp)\r\n throw new TypeError(\r\n \"Both voice gateway and UDP transports are required.\",\r\n );\r\n if (this.gateway || this.udp) this.#closeTransports();\r\n this.gateway = gateway;\r\n this.udp = udp;\r\n // Capture the transport this listener belongs to so that packets from a\r\n // later-replaced transport (whose listener cannot be unregistered — the\r\n // VoiceUdpTransport interface exposes no removal API) are ignored.\r\n const attachedUdp = udp;\r\n if (attachedUdp.onMessage) {\r\n attachedUdp.onMessage((packet) => {\r\n if (this.udp === attachedUdp) this.receiver.onPacket(packet);\r\n });\r\n }\r\n return this;\r\n }\r\n\r\n /** Begins the connection lifecycle. @returns Nothing. @throws {VoiceError} If the connection is destroyed. */\r\n public connect(): void {\r\n this.assertUsable();\r\n if (this.state === VoiceConnectionState.Connected) return;\r\n this.#transition(VoiceConnectionState.Connecting);\r\n if (this.state !== VoiceConnectionState.Connecting) return;\r\n this.#transition(VoiceConnectionState.Connected);\r\n }\r\n\r\n /** Disconnects the current voice session. @returns Nothing. */\r\n public disconnect(): void {\r\n if (this.state === VoiceConnectionState.Destroyed) return;\r\n this.#closeTransports();\r\n this.#transition(VoiceConnectionState.Disconnected);\r\n }\r\n\r\n /** Permanently destroys this voice connection. @returns Nothing. */\r\n public destroy(): void {\r\n if (this.state === VoiceConnectionState.Destroyed) return;\r\n this.#closeTransports();\r\n this.channelId = undefined;\r\n this.#transition(VoiceConnectionState.Destroyed);\r\n this.#listeners.clear();\r\n }\r\n\r\n /** Updates the voice channel and connection state. @param channelId Voice channel identifier. @returns Nothing. @throws {VoiceError} If the connection is destroyed. @throws {TypeError} If channelId is empty. */\r\n public setChannel(channelId: string): void {\r\n this.assertUsable();\r\n if (!channelId?.trim())\r\n throw new TypeError(\"A voice channel ID is required.\");\r\n this.channelId = channelId;\r\n }\r\n\r\n /** Updates self mute/deaf state. @param options Suppression flags. @returns Nothing. @throws {VoiceError} If the connection is destroyed. */\r\n public setSuppression(options: {\r\n selfMute?: boolean;\r\n selfDeaf?: boolean;\r\n }): void {\r\n this.assertUsable();\r\n if (options.selfMute !== undefined) this.selfMute = options.selfMute;\r\n if (options.selfDeaf !== undefined) this.selfDeaf = options.selfDeaf;\r\n }\r\n\r\n /** Publishes a speaking state through the attached voice gateway. @param flags Speaking flags. @returns Nothing. @throws {VoiceError} If the connection is destroyed or has no gateway transport. */\r\n public setSpeaking(flags: number = SpeakingFlags.Microphone): void {\r\n this.assertUsable();\r\n if (!this.gateway) {\r\n throw new VoiceError(\r\n \"A voice gateway transport is required to change speaking state.\",\r\n );\r\n }\r\n this.gateway.send({ op: 5, d: { speaking: flags, delay: 0, ssrc: 0 } });\r\n this.#emit(\"speaking\", flags, 0);\r\n }\r\n\r\n /** Ensures the connection is still usable. @returns Nothing. @throws {VoiceError} If destroyed. */\r\n #assertUsable(): void {\r\n if (this.state === VoiceConnectionState.Destroyed) {\r\n throw new VoiceError(\"Voice connection has been destroyed.\");\r\n }\r\n }\r\n\r\n /** Closes both active transports while isolating cleanup failures. @returns Nothing. */\r\n #closeTransports(): void {\r\n try {\r\n this.gateway?.close(1000, \"Voice connection replaced or closed\");\r\n } catch (error) {\r\n this.#emit(\r\n \"error\",\r\n error instanceof VoiceError\r\n ? error\r\n : new VoiceError(\"Voice gateway cleanup failed.\", {\r\n cause: error,\r\n }),\r\n );\r\n }\r\n try {\r\n this.udp?.close();\r\n } catch (error) {\r\n this.#emit(\r\n \"error\",\r\n error instanceof VoiceError\r\n ? error\r\n : new VoiceError(\"Voice UDP cleanup failed.\", {\r\n cause: error,\r\n }),\r\n );\r\n }\r\n this.gateway = undefined;\r\n this.udp = undefined;\r\n }\r\n\r\n /** Transitions to a new lifecycle state and emits the transition event. @param next Next state. @returns Nothing. */\r\n #transition(next: VoiceConnectionState): void {\r\n const previous = this.state;\r\n this.state = next;\r\n if (previous === next) return;\r\n for (const listener of this.#listeners.get(\"stateChange\") ?? []) {\r\n try {\r\n void listener(next, previous);\r\n } catch (error) {\r\n this.#emit(\r\n \"error\",\r\n error instanceof VoiceError\r\n ? error\r\n : new VoiceError(\"Voice event listener failed.\", {\r\n cause: error,\r\n }),\r\n );\r\n }\r\n if (this.state !== next && next !== VoiceConnectionState.Destroyed)\r\n return;\r\n }\r\n }\r\n\r\n /** Emits a voice event while isolating consumer failures. @param event Event name. @param args Event arguments. @returns Nothing. */\r\n #emit<K extends keyof VoiceEvents>(\r\n event: K,\r\n ...args: VoiceEvents[K]\r\n ): void {\r\n for (const listener of this.#listeners.get(event) ?? []) {\r\n try {\r\n void listener(...args);\r\n } catch {\r\n /* Consumer failures are isolated. */\r\n }\r\n }\r\n }\r\n\r\n /** Ensures the connection is still usable. @returns Nothing. @throws {VoiceError} If destroyed. */\r\n private assertUsable(): void {\r\n this.#assertUsable();\r\n }\r\n}\r\n\r\n// ─── Audio Pipeline ───────────────────────────────────────────────────────────\r\n\r\n/** Audio player lifecycle states. */\r\nexport type AudioPlayerState = \"idle\" | \"playing\" | \"paused\" | \"stopped\";\r\n\r\n/** An audio stream — wraps a `ReadableStream<Uint8Array>` with optional metadata. */\r\nexport class AudioStream {\r\n /** Underlying binary stream. */ public readonly stream: ReadableStream<Uint8Array>;\r\n /** Optional human-readable title for the stream. */ public readonly title?: string;\r\n /** Optional duration hint in seconds. */ public readonly duration?: number;\r\n\r\n /** Creates an audio stream.\r\n * @param stream Binary audio data stream.\r\n * @param metadata Optional metadata: title and duration in seconds.\r\n */\r\n public constructor(\r\n stream: ReadableStream<Uint8Array>,\r\n metadata?: { title?: string; duration?: number },\r\n ) {\r\n this.stream = stream;\r\n this.title = metadata?.title;\r\n this.duration = metadata?.duration;\r\n }\r\n}\r\n\r\ntype AudioPlayerListener<K extends keyof AudioPlayerEvents> = (\r\n ...args: AudioPlayerEvents[K]\r\n) => unknown;\r\n\r\ninterface AudioPlayerEvents {\r\n stateChange: [from: AudioPlayerState, to: AudioPlayerState];\r\n error: [error: Error];\r\n finish: [];\r\n}\r\n\r\n/**\r\n * Lightweight audio player with a simple state machine.\r\n *\r\n * States:\r\n * - `idle` — no stream loaded.\r\n * - `playing` — actively feeding audio data.\r\n * - `paused` — temporarily suspended; can be resumed.\r\n * - `stopped` — stream was stopped and discarded; load a new stream to play again.\r\n *\r\n * Events: `stateChange(from, to)`, `error(err)`, `finish()`.\r\n */\r\nexport class AudioPlayer {\r\n #state: AudioPlayerState = \"idle\";\r\n #stream?: AudioStream;\r\n #reader?: ReadableStreamDefaultReader<Uint8Array>;\r\n /** Resolves the pump's pause await when the player is resumed or stopped. */\r\n #resumeSignal?: () => void;\r\n readonly #listeners = new Map<\r\n string,\r\n Set<AudioPlayerListener<keyof AudioPlayerEvents>>\r\n >();\r\n\r\n /** Current player state. */\r\n public get state(): AudioPlayerState {\r\n return this.#state;\r\n }\r\n\r\n /** The audio stream currently loaded, if any. */\r\n public get current(): AudioStream | undefined {\r\n return this.#stream;\r\n }\r\n\r\n /**\r\n * Begins playing an audio stream.\r\n * If a stream is already playing, it is stopped first; the previous\r\n * stream's reader cancellation is awaited before the new stream starts so\r\n * the old and new pump loops cannot run concurrently.\r\n * @param audioStream Stream to play.\r\n */\r\n public async play(audioStream: AudioStream): Promise<void> {\r\n if (this.#state === \"playing\" || this.#state === \"paused\")\r\n await this.stop();\r\n this.#stream = audioStream;\r\n this.#reader = audioStream.stream.getReader();\r\n this.#transition(\"playing\");\r\n this.#pump().catch((err) => this.#emitError(err));\r\n }\r\n\r\n /** Pauses a currently-playing stream. No-op if not playing. */\r\n public pause(): void {\r\n if (this.#state !== \"playing\") return;\r\n this.#transition(\"paused\");\r\n }\r\n\r\n /** Resumes a paused stream. No-op if not paused. */\r\n public resume(): void {\r\n if (this.#state !== \"paused\") return;\r\n this.#transition(\"playing\");\r\n // Wake the existing pump loop rather than starting a second one.\r\n const signal = this.#resumeSignal;\r\n this.#resumeSignal = undefined;\r\n signal?.();\r\n }\r\n\r\n /**\r\n * Stops and discards the current stream. The returned promise resolves once\r\n * the underlying reader's cancellation has settled, so a subsequent `play`\r\n * never overlaps with the previous pump loop.\r\n */\r\n public async stop(): Promise<void> {\r\n if (this.#state === \"idle\" || this.#state === \"stopped\") return;\r\n const reader = this.#reader;\r\n this.#reader = undefined;\r\n this.#stream = undefined;\r\n this.#transition(\"stopped\");\r\n // Release a pump loop that is currently awaiting a resume.\r\n const signal = this.#resumeSignal;\r\n this.#resumeSignal = undefined;\r\n signal?.();\r\n if (reader) {\r\n try {\r\n await reader.cancel();\r\n } catch {\r\n /* Cancellation failures are non-fatal. */\r\n }\r\n }\r\n }\r\n\r\n /** Registers an event listener. @returns `this` for chaining. */\r\n public on<K extends keyof AudioPlayerEvents>(\r\n event: K,\r\n listener: AudioPlayerListener<K>,\r\n ): this {\r\n let set = this.#listeners.get(event) as\r\n Set<AudioPlayerListener<K>> | undefined;\r\n if (!set) {\r\n this.#listeners.set(\r\n event,\r\n (set = new Set() as unknown as Set<\r\n AudioPlayerListener<keyof AudioPlayerEvents>\r\n >),\r\n );\r\n }\r\n (set as unknown as Set<AudioPlayerListener<K>>).add(listener);\r\n return this;\r\n }\r\n\r\n /** Removes an event listener. @returns `this` for chaining. */\r\n public off<K extends keyof AudioPlayerEvents>(\r\n event: K,\r\n listener: AudioPlayerListener<K>,\r\n ): this {\r\n (\r\n this.#listeners.get(event) as\r\n Set<AudioPlayerListener<K>> | undefined\r\n )?.delete(listener);\r\n return this;\r\n }\r\n\r\n /**\r\n * Handles a decoded audio chunk. The base implementation is a no-op;\r\n * subclasses override this to pipe audio to a UDP socket or other sink.\r\n * @param chunk A chunk of audio bytes read from the current stream.\r\n */\r\n protected onChunk(chunk: Uint8Array): void {\r\n void chunk;\r\n }\r\n\r\n /** Reads chunks from the reader while the player is in a playing state. */\r\n async #pump(): Promise<void> {\r\n const reader = this.#reader;\r\n if (!reader) return;\r\n while (true) {\r\n // Suspend without polling: park until resume()/stop() signals us.\r\n if (this.#state === \"paused\") {\r\n await new Promise<void>((resolve) => {\r\n this.#resumeSignal = resolve;\r\n });\r\n }\r\n // Abort if stopped/idle (e.g. resolved by stop()).\r\n if (this.#state !== \"playing\") return;\r\n const { done, value } = await reader.read();\r\n if (done) {\r\n if (this.#state === \"playing\" || this.#state === \"paused\") {\r\n this.#transition(\"idle\");\r\n this.#stream = undefined;\r\n this.#reader = undefined;\r\n this.#emit(\"finish\");\r\n }\r\n return;\r\n }\r\n // A stop() during the read wins — drop the chunk and exit.\r\n if (this.#state !== \"playing\" && this.#state !== \"paused\") return;\r\n // Deliver the chunk to the sink hook; loop back to honour pause state.\r\n this.onChunk(value);\r\n }\r\n }\r\n\r\n #transition(next: AudioPlayerState): void {\r\n if (this.#state === next) return;\r\n const from = this.#state;\r\n this.#state = next;\r\n this.#emit(\"stateChange\", from, next);\r\n }\r\n\r\n #emitError(err: unknown): void {\r\n this.#emit(\r\n \"error\",\r\n err instanceof Error ? err : new VoiceError(String(err)),\r\n );\r\n }\r\n\r\n #emit<K extends keyof AudioPlayerEvents>(\r\n event: K,\r\n ...args: AudioPlayerEvents[K]\r\n ): void {\r\n for (const listener of this.#listeners.get(event) ?? []) {\r\n try {\r\n void (listener as (...a: unknown[]) => unknown)(...args);\r\n } catch {\r\n /* Listener failures are isolated. */\r\n }\r\n }\r\n }\r\n}\r\n\r\n// ─── Audio Receive Pipeline ───────────────────────────────────────────────────\r\n\r\n/** Tracks incoming audio and maps SSRCs to readable streams per user. */\r\nexport class VoiceReceiver {\r\n /** The connection this receiver is attached to. */\r\n public readonly connection: VoiceConnection;\r\n readonly #ssrcMap = new Map<number, string>();\r\n /** Active stream controllers per user; a Set so multiple subscribers to the\r\n * same user all receive audio instead of the last one overwriting earlier ones. */\r\n readonly #streams = new Map<\r\n string,\r\n Set<ReadableStreamDefaultController<Uint8Array>>\r\n >();\r\n\r\n /** Creates a VoiceReceiver. @param connection Voice connection. */\r\n public constructor(connection: VoiceConnection) {\r\n this.connection = connection;\r\n }\r\n\r\n /** Maps an SSRC to a User ID (usually from gateway speaking events). */\r\n public mapSsrc(ssrc: number, userId: string): void {\r\n this.#ssrcMap.set(ssrc, userId);\r\n }\r\n\r\n /** Subscribes to incoming audio from a specific user. @param userId User identifier. @returns A binary readable stream of the audio. */\r\n public subscribe(userId: string): AudioStream {\r\n let controllerRef: ReadableStreamDefaultController<Uint8Array>;\r\n const stream = new ReadableStream<Uint8Array>({\r\n start: (controller) => {\r\n controllerRef = controller;\r\n let set = this.#streams.get(userId);\r\n if (!set) this.#streams.set(userId, (set = new Set()));\r\n set.add(controller);\r\n },\r\n cancel: () => {\r\n const set = this.#streams.get(userId);\r\n if (set) {\r\n set.delete(controllerRef);\r\n if (set.size === 0) this.#streams.delete(userId);\r\n }\r\n },\r\n });\r\n return new AudioStream(stream, { title: `User ${userId} Audio` });\r\n }\r\n\r\n /** Processes an incoming UDP packet, extracting SSRC and routing the audio\r\n * payload (RTP header stripped) to every subscriber for the mapped user. */\r\n public onPacket(packet: Uint8Array): void {\r\n if (packet.length < 12) return;\r\n\r\n const view = new DataView(\r\n packet.buffer,\r\n packet.byteOffset,\r\n packet.byteLength,\r\n );\r\n const ssrc = view.getUint32(8, false);\r\n\r\n const userId = this.#ssrcMap.get(ssrc);\r\n if (!userId) return;\r\n const controllers = this.#streams.get(userId);\r\n if (!controllers || controllers.size === 0) return;\r\n\r\n // Compute the RTP payload offset: 12-byte fixed header + CSRC list, plus\r\n // the extension header when the X bit is set.\r\n const csrcCount = packet[0] & 0x0f;\r\n let offset = 12 + csrcCount * 4;\r\n const hasExtension = (packet[0] & 0x10) !== 0;\r\n if (hasExtension) {\r\n if (packet.length < offset + 4) return;\r\n const extWords = view.getUint16(offset + 2, false);\r\n offset += 4 + extWords * 4;\r\n }\r\n if (packet.length < offset) return;\r\n const payload = packet.slice(offset);\r\n\r\n for (const controller of controllers) {\r\n try {\r\n controller.enqueue(payload);\r\n } catch {\r\n // Ignore errors on closed streams.\r\n }\r\n }\r\n }\r\n}\r\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;AACO,IAAK;AAAA,CAAL,CAAK,0BAAL;AAAA,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "9C1109F35D4A667564756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/ws/index.d.ts
CHANGED
|
@@ -13,6 +13,29 @@ export declare const GatewayOpcodes: {
|
|
|
13
13
|
readonly Hello: 10;
|
|
14
14
|
readonly HeartbeatAck: 11;
|
|
15
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Discord Gateway close codes.
|
|
18
|
+
*
|
|
19
|
+
* Discord.js-familiar names and numeric values, matching the Discord Gateway
|
|
20
|
+
* protocol. Exposed so consumers can branch on named codes instead of magic
|
|
21
|
+
* numbers; {@link Gateway} uses them internally to decide resume/identify/stop.
|
|
22
|
+
*/
|
|
23
|
+
export declare const GatewayCloseCodes: {
|
|
24
|
+
readonly UnknownError: 4000;
|
|
25
|
+
readonly UnknownOpcode: 4001;
|
|
26
|
+
readonly DecodeError: 4002;
|
|
27
|
+
readonly NotAuthenticated: 4003;
|
|
28
|
+
readonly AuthenticationFailed: 4004;
|
|
29
|
+
readonly AlreadyAuthenticated: 4005;
|
|
30
|
+
readonly InvalidSeq: 4007;
|
|
31
|
+
readonly RateLimited: 4008;
|
|
32
|
+
readonly SessionTimedOut: 4009;
|
|
33
|
+
readonly InvalidShard: 4010;
|
|
34
|
+
readonly ShardingRequired: 4011;
|
|
35
|
+
readonly InvalidAPIVersion: 4012;
|
|
36
|
+
readonly InvalidIntents: 4013;
|
|
37
|
+
readonly DisallowedIntents: 4014;
|
|
38
|
+
};
|
|
16
39
|
/** Gateway connection lifecycle states. */
|
|
17
40
|
export declare enum GatewayState {
|
|
18
41
|
/** Initial connection state. */ Connect = "CONNECT",
|
|
@@ -25,6 +48,16 @@ export declare enum GatewayState {
|
|
|
25
48
|
/** Reconnect in progress. */ Reconnect = "RECONNECT",
|
|
26
49
|
/** Gateway is permanently closed. */ Closed = "CLOSED"
|
|
27
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Discord.js-familiar alias for {@link GatewayState}.
|
|
53
|
+
*
|
|
54
|
+
* Discord.js exposes connection status via a `Status` enum. Lunibee's canonical
|
|
55
|
+
* name is {@link GatewayState}; this is an additive alias so `discord.js` users
|
|
56
|
+
* find the expected name. Note the *values* remain Lunibee's string states
|
|
57
|
+
* (e.g. `"READY"`), not Discord.js's numeric `Status` members — an intentional
|
|
58
|
+
* divergence documented in the compatibility matrix.
|
|
59
|
+
*/
|
|
60
|
+
export { GatewayState as Status };
|
|
28
61
|
/** Gateway protocol error. */
|
|
29
62
|
export declare class GatewayError extends Error {
|
|
30
63
|
/** Gateway close/error code. */ readonly code?: number;
|
|
@@ -44,6 +77,12 @@ export interface GatewayOptions {
|
|
|
44
77
|
/** Zombie connection timeout. */ zombieTimeout?: number;
|
|
45
78
|
/** Identification properties. */ properties?: GatewayProperties;
|
|
46
79
|
/** Presence data. */ presence?: GatewayPresence;
|
|
80
|
+
/**
|
|
81
|
+
* Whether to enable zlib-stream transport compression.
|
|
82
|
+
* Uses Bun's native `DecompressionStream` — no external dependencies.
|
|
83
|
+
* When enabled, appends `&compress=zlib-stream` to the Gateway URL.
|
|
84
|
+
*/
|
|
85
|
+
compress?: boolean;
|
|
47
86
|
}
|
|
48
87
|
/** Gateway event listener. */
|
|
49
88
|
type GatewayListener = (data: unknown) => unknown;
|
|
@@ -52,6 +91,7 @@ export declare class Gateway {
|
|
|
52
91
|
#private;
|
|
53
92
|
/** Current Gateway lifecycle state. */
|
|
54
93
|
state: GatewayState;
|
|
94
|
+
ping: number;
|
|
55
95
|
/** Creates a Gateway connection manager. @param options Gateway configuration. @throws {TypeError|RangeError} If configuration is invalid. */
|
|
56
96
|
constructor(options: GatewayOptions);
|
|
57
97
|
/** Opens the Gateway connection. @param url Gateway WebSocket URL. @returns Promise fulfilled when the socket opens. @throws {GatewayError} If permanently closed or unable to connect. */
|
|
@@ -73,4 +113,3 @@ export declare class Gateway {
|
|
|
73
113
|
/** Requests guild members. @param data Guild member request payload. @returns Whether it was sent. */
|
|
74
114
|
requestGuildMembers(data: Record<string, unknown>): boolean;
|
|
75
115
|
}
|
|
76
|
-
export {};
|
package/dist/ws/index.js
CHANGED
|
@@ -88,6 +88,22 @@ var GatewayOpcodes = {
|
|
|
88
88
|
Hello: 10,
|
|
89
89
|
HeartbeatAck: 11
|
|
90
90
|
};
|
|
91
|
+
var GatewayCloseCodes = {
|
|
92
|
+
UnknownError: 4000,
|
|
93
|
+
UnknownOpcode: 4001,
|
|
94
|
+
DecodeError: 4002,
|
|
95
|
+
NotAuthenticated: 4003,
|
|
96
|
+
AuthenticationFailed: 4004,
|
|
97
|
+
AlreadyAuthenticated: 4005,
|
|
98
|
+
InvalidSeq: 4007,
|
|
99
|
+
RateLimited: 4008,
|
|
100
|
+
SessionTimedOut: 4009,
|
|
101
|
+
InvalidShard: 4010,
|
|
102
|
+
ShardingRequired: 4011,
|
|
103
|
+
InvalidAPIVersion: 4012,
|
|
104
|
+
InvalidIntents: 4013,
|
|
105
|
+
DisallowedIntents: 4014
|
|
106
|
+
};
|
|
91
107
|
var GatewayState;
|
|
92
108
|
((GatewayState2) => {
|
|
93
109
|
GatewayState2["Connect"] = "CONNECT";
|
|
@@ -100,7 +116,6 @@ var GatewayState;
|
|
|
100
116
|
GatewayState2["Reconnect"] = "RECONNECT";
|
|
101
117
|
GatewayState2["Closed"] = "CLOSED";
|
|
102
118
|
})(GatewayState ||= {});
|
|
103
|
-
|
|
104
119
|
class GatewayError extends Error {
|
|
105
120
|
code;
|
|
106
121
|
constructor(message, code, options) {
|
|
@@ -134,6 +149,8 @@ class Gateway {
|
|
|
134
149
|
#rejectConnect;
|
|
135
150
|
#listeners = new Map;
|
|
136
151
|
#sendTimestamps = [];
|
|
152
|
+
ping = -1;
|
|
153
|
+
#decompressor;
|
|
137
154
|
constructor(options) {
|
|
138
155
|
if (!options.token?.trim())
|
|
139
156
|
throw new TypeError("A Gateway token is required.");
|
|
@@ -154,7 +171,12 @@ class Gateway {
|
|
|
154
171
|
browser: "Discord Android",
|
|
155
172
|
device: "Discord Android"
|
|
156
173
|
},
|
|
157
|
-
presence: {
|
|
174
|
+
presence: {
|
|
175
|
+
status: "online",
|
|
176
|
+
activities: [],
|
|
177
|
+
afk: false,
|
|
178
|
+
since: null
|
|
179
|
+
},
|
|
158
180
|
...options
|
|
159
181
|
};
|
|
160
182
|
if (this.#options.shardId < 0 || this.#options.shardId >= this.#options.shardCount)
|
|
@@ -169,10 +191,13 @@ class Gateway {
|
|
|
169
191
|
return this.#connectPromise;
|
|
170
192
|
this.#closed = false;
|
|
171
193
|
this.#setState("CONNECT" /* Connect */);
|
|
194
|
+
const connectURL = this.#options.compress ? url.includes("compress=") ? url : `${url}&compress=zlib-stream` : url;
|
|
195
|
+
if (this.#options.compress)
|
|
196
|
+
this.#initDecompressor();
|
|
172
197
|
this.#connectPromise = new Promise((resolve, reject) => {
|
|
173
198
|
this.#resolveConnect = resolve;
|
|
174
199
|
this.#rejectConnect = reject;
|
|
175
|
-
this.#open(
|
|
200
|
+
this.#open(connectURL);
|
|
176
201
|
});
|
|
177
202
|
return this.#connectPromise;
|
|
178
203
|
}
|
|
@@ -208,12 +233,15 @@ class Gateway {
|
|
|
208
233
|
send(payload) {
|
|
209
234
|
if (!payload || !Number.isInteger(payload.op))
|
|
210
235
|
throw new TypeError("Gateway payload must contain an integer opcode.");
|
|
236
|
+
return this.#dispatch(payload, false);
|
|
237
|
+
}
|
|
238
|
+
#dispatch(payload, privileged) {
|
|
211
239
|
if (this.#ws?.readyState !== WebSocket.OPEN)
|
|
212
240
|
return false;
|
|
213
241
|
const now = Date.now();
|
|
214
242
|
while (this.#sendTimestamps.length && now - this.#sendTimestamps[0] >= 60000)
|
|
215
243
|
this.#sendTimestamps.shift();
|
|
216
|
-
if (this.#sendTimestamps.length >= 115) {
|
|
244
|
+
if (!privileged && this.#sendTimestamps.length >= 115) {
|
|
217
245
|
this.#emitError(new GatewayError("Gateway send rate budget exhausted."));
|
|
218
246
|
return false;
|
|
219
247
|
}
|
|
@@ -284,7 +312,11 @@ class Gateway {
|
|
|
284
312
|
ws.addEventListener("message", (event) => {
|
|
285
313
|
this.#lastMessageAt = Date.now();
|
|
286
314
|
this.#zombieReported = false;
|
|
287
|
-
this.#
|
|
315
|
+
if (this.#options.compress && event.data instanceof ArrayBuffer) {
|
|
316
|
+
this.#decompress(new Uint8Array(event.data)).then((text) => this.#message(ws, text)).catch((err) => this.#emitError(err));
|
|
317
|
+
} else {
|
|
318
|
+
this.#message(ws, String(event.data));
|
|
319
|
+
}
|
|
288
320
|
});
|
|
289
321
|
ws.addEventListener("close", (event) => this.#close(ws, event.code));
|
|
290
322
|
ws.addEventListener("error", () => this.#emitError(new GatewayError("Gateway WebSocket error")));
|
|
@@ -292,7 +324,7 @@ class Gateway {
|
|
|
292
324
|
#startZombieDetection() {
|
|
293
325
|
if (this.#zombieTimer)
|
|
294
326
|
clearInterval(this.#zombieTimer);
|
|
295
|
-
const interval = Math.max(1, Math.min(
|
|
327
|
+
const interval = Math.max(1, Math.min(1000, this.#options.heartbeatAckTimeout, this.#options.zombieTimeout / 2));
|
|
296
328
|
this.#zombieTimer = setInterval(() => {
|
|
297
329
|
if (this.#closed || this.#ws?.readyState !== WebSocket.OPEN || this.#lastMessageAt === 0)
|
|
298
330
|
return;
|
|
@@ -343,18 +375,24 @@ class Gateway {
|
|
|
343
375
|
break;
|
|
344
376
|
case GatewayOpcodes.HeartbeatAck:
|
|
345
377
|
this.#heartbeatACK = true;
|
|
378
|
+
this.ping = Date.now() - this.#heartbeatSentAt;
|
|
346
379
|
this.#clearHeartbeatAckTimer();
|
|
347
380
|
this.#emit("heartbeatAck", payload.d);
|
|
348
381
|
break;
|
|
349
382
|
case GatewayOpcodes.Reconnect:
|
|
350
383
|
ws.close(1001, "Server requested reconnect");
|
|
351
384
|
break;
|
|
352
|
-
case GatewayOpcodes.InvalidSession:
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
385
|
+
case GatewayOpcodes.InvalidSession: {
|
|
386
|
+
const resumable = payload.d === true;
|
|
387
|
+
if (!resumable) {
|
|
388
|
+
this.#sessionId = undefined;
|
|
389
|
+
this.#sequence = null;
|
|
390
|
+
this.#resumeURL = undefined;
|
|
391
|
+
}
|
|
392
|
+
this.#emit("invalidSession", resumable);
|
|
393
|
+
ws.close(resumable ? GatewayCloseCodes.UnknownError : 1000, resumable ? "Invalid session (resumable)" : "Invalid session");
|
|
357
394
|
break;
|
|
395
|
+
}
|
|
358
396
|
}
|
|
359
397
|
}
|
|
360
398
|
#handleDispatch(event, data) {
|
|
@@ -369,7 +407,14 @@ class Gateway {
|
|
|
369
407
|
this.#attempt = 0;
|
|
370
408
|
this.#setState("READY" /* Ready */);
|
|
371
409
|
this.#emit("ready", data);
|
|
410
|
+
} else if (event === "RESUMED") {
|
|
411
|
+
this.#attempt = 0;
|
|
412
|
+
this.#setState("READY" /* Ready */);
|
|
413
|
+
this.#emit("resumed", data);
|
|
372
414
|
}
|
|
415
|
+
this.#emit("RAW", { event: event ?? "UNKNOWN", data });
|
|
416
|
+
if (this.#closed)
|
|
417
|
+
return;
|
|
373
418
|
this.#emit(event ?? "dispatch", data);
|
|
374
419
|
}
|
|
375
420
|
#handleHello(data) {
|
|
@@ -443,12 +488,12 @@ class Gateway {
|
|
|
443
488
|
#sendHeartbeat() {
|
|
444
489
|
this.#heartbeatACK = false;
|
|
445
490
|
this.#heartbeatSentAt = Date.now();
|
|
446
|
-
if (!this
|
|
491
|
+
if (!this.#dispatch({
|
|
447
492
|
op: GatewayOpcodes.Heartbeat,
|
|
448
493
|
d: this.#sequence,
|
|
449
494
|
s: null,
|
|
450
495
|
t: null
|
|
451
|
-
})) {
|
|
496
|
+
}, true)) {
|
|
452
497
|
this.#emitError(new GatewayError("Unable to send Gateway heartbeat because the WebSocket is not open."));
|
|
453
498
|
return;
|
|
454
499
|
}
|
|
@@ -481,14 +526,23 @@ class Gateway {
|
|
|
481
526
|
if (action === "identify") {
|
|
482
527
|
this.#sequence = null;
|
|
483
528
|
this.#sessionId = undefined;
|
|
529
|
+
this.#resumeURL = undefined;
|
|
484
530
|
}
|
|
485
531
|
this.#setState("RECONNECT" /* Reconnect */);
|
|
486
532
|
this.#scheduleReconnect();
|
|
487
533
|
}
|
|
488
534
|
#closeAction(code) {
|
|
489
|
-
|
|
535
|
+
const fatal = [
|
|
536
|
+
GatewayCloseCodes.AuthenticationFailed,
|
|
537
|
+
GatewayCloseCodes.InvalidShard,
|
|
538
|
+
GatewayCloseCodes.ShardingRequired,
|
|
539
|
+
GatewayCloseCodes.InvalidAPIVersion,
|
|
540
|
+
GatewayCloseCodes.InvalidIntents,
|
|
541
|
+
GatewayCloseCodes.DisallowedIntents
|
|
542
|
+
];
|
|
543
|
+
if (fatal.includes(code))
|
|
490
544
|
return "stop";
|
|
491
|
-
if (
|
|
545
|
+
if (code === GatewayCloseCodes.InvalidSeq || code === GatewayCloseCodes.SessionTimedOut)
|
|
492
546
|
return "identify";
|
|
493
547
|
return this.#sessionId && this.#sequence !== null ? "resume" : "identify";
|
|
494
548
|
}
|
|
@@ -501,7 +555,7 @@ class Gateway {
|
|
|
501
555
|
return;
|
|
502
556
|
}
|
|
503
557
|
const delay = Math.min(this.#options.reconnectMaxDelay, this.#options.reconnectBaseDelay * 2 ** this.#attempt++);
|
|
504
|
-
const jitter = Math.random() * Math.
|
|
558
|
+
const jitter = Math.random() * Math.max(1, delay * 0.25);
|
|
505
559
|
this.#reconnectTimer = setTimeout(() => {
|
|
506
560
|
this.#reconnectTimer = undefined;
|
|
507
561
|
this.#open(this.#resumeURL ?? "wss://gateway.discord.gg/?v=10&encoding=json");
|
|
@@ -571,12 +625,45 @@ class Gateway {
|
|
|
571
625
|
this.state = next;
|
|
572
626
|
this.#emit("stateChange", { previous, next });
|
|
573
627
|
}
|
|
628
|
+
#initDecompressor() {
|
|
629
|
+
const ds = new DecompressionStream("deflate-raw");
|
|
630
|
+
this.#decompressor = {
|
|
631
|
+
writer: ds.writable.getWriter(),
|
|
632
|
+
reader: ds.readable.getReader()
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
async#decompress(chunk) {
|
|
636
|
+
if (!this.#decompressor)
|
|
637
|
+
this.#initDecompressor();
|
|
638
|
+
const { writer, reader } = this.#decompressor;
|
|
639
|
+
await writer.write(chunk);
|
|
640
|
+
const parts = [];
|
|
641
|
+
while (true) {
|
|
642
|
+
const { done, value } = await Promise.race([
|
|
643
|
+
reader.read(),
|
|
644
|
+
new Promise((resolve) => setTimeout(() => resolve({ done: true, value: undefined }), 50))
|
|
645
|
+
]);
|
|
646
|
+
if (done || !value)
|
|
647
|
+
break;
|
|
648
|
+
parts.push(value);
|
|
649
|
+
}
|
|
650
|
+
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
651
|
+
const merged = new Uint8Array(total);
|
|
652
|
+
let offset = 0;
|
|
653
|
+
for (const p of parts) {
|
|
654
|
+
merged.set(p, offset);
|
|
655
|
+
offset += p.length;
|
|
656
|
+
}
|
|
657
|
+
return new TextDecoder().decode(merged);
|
|
658
|
+
}
|
|
574
659
|
}
|
|
575
660
|
export {
|
|
576
661
|
Gateway,
|
|
662
|
+
GatewayCloseCodes,
|
|
577
663
|
GatewayError,
|
|
578
664
|
GatewayOpcodes,
|
|
579
|
-
GatewayState
|
|
665
|
+
GatewayState,
|
|
666
|
+
GatewayState as Status
|
|
580
667
|
};
|
|
581
668
|
|
|
582
|
-
//# debugId=
|
|
669
|
+
//# debugId=267C8623751A500664756E2164756E21
|