lunibee 0.1.7 → 0.2.0
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/LICENSE +21 -21
- package/README.md +225 -223
- package/dist/builders/commands.d.ts +6 -4
- package/dist/builders/components.d.ts +23 -0
- package/dist/builders/index.d.ts +2 -2
- package/dist/builders/index.js +120 -24
- package/dist/builders/index.js.map +9 -8
- package/dist/collection/index.d.ts +68 -1
- package/dist/collection/index.js +335 -1
- package/dist/collection/index.js.map +4 -4
- package/dist/core/collector.d.ts +13 -0
- package/dist/core/events.d.ts +112 -2
- package/dist/core/index.d.ts +30 -93
- package/dist/core/index.js +3386 -957
- package/dist/core/index.js.map +48 -24
- package/dist/core/permissions.d.ts +48 -0
- package/dist/formatters/index.js.map +1 -1
- package/dist/handlers/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3806 -1193
- package/dist/index.js.map +64 -43
- package/dist/managers/advanced.d.ts +116 -0
- package/dist/managers/base.d.ts +13 -2
- package/dist/managers/emoji.d.ts +1 -1
- package/dist/managers/guild-resources.d.ts +109 -0
- package/dist/managers/guild.d.ts +33 -2
- package/dist/managers/index.d.ts +44 -6
- package/dist/managers/index.js +1186 -150
- package/dist/managers/index.js.map +27 -21
- package/dist/managers/message.d.ts +10 -1
- package/dist/rest/decoder.d.ts +23 -0
- package/dist/rest/errors.d.ts +53 -0
- package/dist/rest/index.d.ts +21 -17
- package/dist/rest/index.js +526 -264
- package/dist/rest/index.js.map +14 -8
- package/dist/rest/limiter.d.ts +42 -0
- package/dist/rest/redis.d.ts +18 -1
- package/dist/rest/route.d.ts +31 -0
- package/dist/rest/routes.d.ts +22 -0
- package/dist/rest/scheduler.d.ts +42 -0
- package/dist/rest/store.d.ts +29 -0
- package/dist/rest/transport.d.ts +40 -0
- package/dist/sharding/bus.d.ts +31 -0
- package/dist/sharding/cluster.d.ts +10 -0
- package/dist/sharding/index.d.ts +39 -2
- package/dist/sharding/index.js +1105 -404
- package/dist/sharding/index.js.map +18 -8
- package/dist/structures/base.d.ts +10 -0
- package/dist/structures/channels.d.ts +56 -0
- package/dist/structures/index.d.ts +1 -0
- package/dist/structures/index.js +305 -311
- package/dist/structures/index.js.map +18 -14
- package/dist/structures/interactions.d.ts +38 -52
- package/dist/structures/options.d.ts +55 -0
- package/dist/structures/resources.d.ts +3 -3
- package/dist/types/gateway-events.d.ts +132 -0
- package/dist/types/gateway.d.ts +197 -0
- package/dist/types/index.d.ts +53 -260
- package/dist/types/index.js +44 -2
- package/dist/types/index.js.map +5 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/voice/index.d.ts +32 -1
- package/dist/voice/index.js +73 -7
- package/dist/voice/index.js.map +3 -3
- package/dist/ws/close-codes.d.ts +23 -0
- package/dist/ws/decoder.d.ts +58 -0
- package/dist/ws/heartbeat.d.ts +87 -0
- package/dist/ws/index.d.ts +18 -65
- package/dist/ws/index.js +911 -385
- package/dist/ws/index.js.map +15 -5
- package/dist/ws/opcodes.d.ts +14 -0
- package/dist/ws/protocol.d.ts +110 -0
- package/dist/ws/reconnect.d.ts +108 -0
- package/dist/ws/send-budget.d.ts +17 -0
- package/dist/ws/session.d.ts +100 -0
- package/dist/ws/state.d.ts +27 -0
- package/dist/ws/transport.d.ts +80 -0
- package/package.json +21 -20
package/dist/voice/index.d.ts
CHANGED
|
@@ -178,8 +178,39 @@ export declare class VoiceReceiver {
|
|
|
178
178
|
mapSsrc(ssrc: number, userId: string): void;
|
|
179
179
|
/** Subscribes to incoming audio from a specific user. @param userId User identifier. @returns A binary readable stream of the audio. */
|
|
180
180
|
subscribe(userId: string): AudioStream;
|
|
181
|
+
/** Ends every subscriber stream and forgets SSRC mappings. Called when the
|
|
182
|
+
* connection disconnects or is destroyed, so readers do not wait forever. */
|
|
183
|
+
close(): void;
|
|
181
184
|
/** Processes an incoming UDP packet, extracting SSRC and routing the audio
|
|
182
185
|
* payload (RTP header stripped) to every subscriber for the mapped user. */
|
|
183
186
|
onPacket(packet: Uint8Array): void;
|
|
184
187
|
}
|
|
185
|
-
|
|
188
|
+
/** Discord.js-familiar alias for {@link VoiceConnectionState}. Member values are Lunibee's. */
|
|
189
|
+
export { VoiceConnectionState as VoiceConnectionStatus };
|
|
190
|
+
/**
|
|
191
|
+
* Joins (or moves to) a voice channel, reusing the guild's tracked connection.
|
|
192
|
+
* Mirrors `@discordjs/voice`'s `joinVoiceChannel`; transports are still attached
|
|
193
|
+
* with {@link VoiceConnection.attachTransports}.
|
|
194
|
+
* @param options Guild, channel and self mute/deaf flags.
|
|
195
|
+
* @returns The guild's voice connection.
|
|
196
|
+
* @throws {TypeError} If the guild or channel ID is empty.
|
|
197
|
+
*/
|
|
198
|
+
export declare function joinVoiceChannel(options: VoiceConnectionOptions & {
|
|
199
|
+
channelId: string;
|
|
200
|
+
}): VoiceConnection;
|
|
201
|
+
/** Returns the connection tracked by {@link joinVoiceChannel} for a guild, if any. */
|
|
202
|
+
export declare function getVoiceConnection(guildId: string): VoiceConnection | undefined;
|
|
203
|
+
/** Creates an {@link AudioPlayer}. Mirrors `@discordjs/voice`'s `createAudioPlayer`. */
|
|
204
|
+
export declare function createAudioPlayer(): AudioPlayer;
|
|
205
|
+
/** Creates an {@link AudioStream}. Mirrors `@discordjs/voice`'s `createAudioResource`. */
|
|
206
|
+
export declare function createAudioResource(stream: ReadableStream<Uint8Array>, metadata?: {
|
|
207
|
+
title?: string;
|
|
208
|
+
duration?: number;
|
|
209
|
+
}): AudioStream;
|
|
210
|
+
/**
|
|
211
|
+
* Resolves once a connection or player reaches `status`, or rejects after `timeoutMs`.
|
|
212
|
+
* Mirrors `@discordjs/voice`'s `entersState`.
|
|
213
|
+
* @returns The same target, for chaining.
|
|
214
|
+
* @throws {VoiceError} If the state is not reached in time.
|
|
215
|
+
*/
|
|
216
|
+
export declare function entersState<T extends VoiceConnection | AudioPlayer>(target: T, status: T extends VoiceConnection ? VoiceConnectionState : AudioPlayerState, timeoutMs: number): Promise<T>;
|
package/dist/voice/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/voice/src/index.ts
|
|
3
3
|
var VoiceConnectionState;
|
|
4
|
-
((
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
((VoiceConnectionState) => {
|
|
5
|
+
VoiceConnectionState["Disconnected"] = "disconnected";
|
|
6
|
+
VoiceConnectionState["Connecting"] = "connecting";
|
|
7
|
+
VoiceConnectionState["Connected"] = "connected";
|
|
8
|
+
VoiceConnectionState["Destroyed"] = "destroyed";
|
|
9
9
|
})(VoiceConnectionState ||= {});
|
|
10
10
|
|
|
11
11
|
class VoiceError extends Error {
|
|
@@ -84,12 +84,14 @@ class VoiceConnection {
|
|
|
84
84
|
if (this.state === "destroyed" /* Destroyed */)
|
|
85
85
|
return;
|
|
86
86
|
this.#closeTransports();
|
|
87
|
+
this.receiver.close();
|
|
87
88
|
this.#transition("disconnected" /* Disconnected */);
|
|
88
89
|
}
|
|
89
90
|
destroy() {
|
|
90
91
|
if (this.state === "destroyed" /* Destroyed */)
|
|
91
92
|
return;
|
|
92
93
|
this.#closeTransports();
|
|
94
|
+
this.receiver.close();
|
|
93
95
|
this.channelId = undefined;
|
|
94
96
|
this.#transition("destroyed" /* Destroyed */);
|
|
95
97
|
this.#listeners.clear();
|
|
@@ -297,6 +299,8 @@ class VoiceReceiver {
|
|
|
297
299
|
this.#ssrcMap.set(ssrc, userId);
|
|
298
300
|
}
|
|
299
301
|
subscribe(userId) {
|
|
302
|
+
if (this.connection.state === "destroyed" /* Destroyed */)
|
|
303
|
+
throw new VoiceError("Voice connection has been destroyed.");
|
|
300
304
|
let controllerRef;
|
|
301
305
|
const stream = new ReadableStream({
|
|
302
306
|
start: (controller) => {
|
|
@@ -317,6 +321,16 @@ class VoiceReceiver {
|
|
|
317
321
|
});
|
|
318
322
|
return new AudioStream(stream, { title: `User ${userId} Audio` });
|
|
319
323
|
}
|
|
324
|
+
close() {
|
|
325
|
+
for (const controllers of this.#streams.values())
|
|
326
|
+
for (const controller of controllers) {
|
|
327
|
+
try {
|
|
328
|
+
controller.close();
|
|
329
|
+
} catch {}
|
|
330
|
+
}
|
|
331
|
+
this.#streams.clear();
|
|
332
|
+
this.#ssrcMap.clear();
|
|
333
|
+
}
|
|
320
334
|
onPacket(packet) {
|
|
321
335
|
if (packet.length < 12)
|
|
322
336
|
return;
|
|
@@ -347,14 +361,66 @@ class VoiceReceiver {
|
|
|
347
361
|
}
|
|
348
362
|
}
|
|
349
363
|
}
|
|
364
|
+
var voiceConnections = new Map;
|
|
365
|
+
function joinVoiceChannel(options) {
|
|
366
|
+
let connection = voiceConnections.get(options.guildId);
|
|
367
|
+
if (!connection) {
|
|
368
|
+
connection = new VoiceConnection(options);
|
|
369
|
+
const created = connection;
|
|
370
|
+
created.on("stateChange", (next) => {
|
|
371
|
+
if (next === "destroyed" /* Destroyed */ && voiceConnections.get(created.guildId) === created)
|
|
372
|
+
voiceConnections.delete(created.guildId);
|
|
373
|
+
});
|
|
374
|
+
voiceConnections.set(options.guildId, connection);
|
|
375
|
+
}
|
|
376
|
+
connection.setChannel(options.channelId);
|
|
377
|
+
connection.setSuppression(options);
|
|
378
|
+
connection.connect();
|
|
379
|
+
return connection;
|
|
380
|
+
}
|
|
381
|
+
function getVoiceConnection(guildId) {
|
|
382
|
+
return voiceConnections.get(guildId);
|
|
383
|
+
}
|
|
384
|
+
function createAudioPlayer() {
|
|
385
|
+
return new AudioPlayer;
|
|
386
|
+
}
|
|
387
|
+
function createAudioResource(stream, metadata) {
|
|
388
|
+
return new AudioStream(stream, metadata);
|
|
389
|
+
}
|
|
390
|
+
function entersState(target, status, timeoutMs) {
|
|
391
|
+
if (target.state === status)
|
|
392
|
+
return Promise.resolve(target);
|
|
393
|
+
const emitter = target;
|
|
394
|
+
const isConnection = target instanceof VoiceConnection;
|
|
395
|
+
return new Promise((resolve, reject) => {
|
|
396
|
+
const onChange = (first, second) => {
|
|
397
|
+
if ((isConnection ? first : second) !== status)
|
|
398
|
+
return;
|
|
399
|
+
clearTimeout(timer);
|
|
400
|
+
emitter.off("stateChange", onChange);
|
|
401
|
+
resolve(target);
|
|
402
|
+
};
|
|
403
|
+
const timer = setTimeout(() => {
|
|
404
|
+
emitter.off("stateChange", onChange);
|
|
405
|
+
reject(new VoiceError(`Did not enter state "${String(status)}" within ${timeoutMs}ms.`));
|
|
406
|
+
}, timeoutMs);
|
|
407
|
+
emitter.on("stateChange", onChange);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
350
410
|
export {
|
|
351
411
|
AudioPlayer,
|
|
352
412
|
AudioStream,
|
|
353
413
|
SpeakingFlags,
|
|
354
414
|
VoiceConnection,
|
|
355
415
|
VoiceConnectionState,
|
|
416
|
+
VoiceConnectionState as VoiceConnectionStatus,
|
|
356
417
|
VoiceError,
|
|
357
|
-
VoiceReceiver
|
|
418
|
+
VoiceReceiver,
|
|
419
|
+
createAudioPlayer,
|
|
420
|
+
createAudioResource,
|
|
421
|
+
entersState,
|
|
422
|
+
getVoiceConnection,
|
|
423
|
+
joinVoiceChannel
|
|
358
424
|
};
|
|
359
425
|
|
|
360
|
-
//# debugId=
|
|
426
|
+
//# debugId=9E96BE25F2F4C69B64756E2164756E21
|
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 /** 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"
|
|
5
|
+
"/** Voice session lifecycle states. */\nexport enum VoiceConnectionState {\n Disconnected = \"disconnected\",\n Connecting = \"connecting\",\n Connected = \"connected\",\n Destroyed = \"destroyed\",\n}\n\n/** Voice protocol transport errors. */\nexport class VoiceError extends Error {\n /** Creates a voice error. @param message Error message. @param options Optional native error options. */\n public constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"VoiceError\";\n }\n}\n\n/** Options used to establish a voice session. */\nexport interface VoiceConnectionOptions {\n /** Guild containing the voice channel. */\n guildId: string;\n /** Voice channel to join. */\n channelId?: string;\n /** Whether the self user should be muted. */\n selfMute?: boolean;\n /** Whether the self user should be deafened. */\n selfDeaf?: boolean;\n}\n\n/** UDP transport supplied by the runtime-specific voice implementation. */\nexport interface VoiceUdpTransport {\n /** Sends an RTP/RTCP packet. @param packet Packet bytes. @param host Destination host. @param port Destination port. @returns Optional completion promise. */\n send(packet: Uint8Array, host: string, port: number): Promise<void> | void;\n /** Closes the UDP socket. @returns Nothing. */\n close(): void;\n /** Optional listener for incoming UDP packets. */\n onMessage?(listener: (packet: Uint8Array) => void): void;\n}\n\n/** Voice Gateway transport abstraction. */\nexport interface VoiceGatewayTransport {\n /** Opens the voice gateway. @param endpoint Voice gateway endpoint. @param sessionId Voice session identifier. @returns Promise fulfilled when connected. */\n connect(endpoint: string, sessionId: string): Promise<void>;\n /** Sends a voice gateway payload. @param payload Gateway payload. @returns Nothing. */\n send(payload: unknown): void;\n /** Closes the voice gateway. @param code Optional close code. @param reason Optional close reason. @returns Nothing. */\n close(code?: number, reason?: string): void;\n}\n\n/** Speaking flags used by the voice gateway. */\nexport const SpeakingFlags = {\n Microphone: 1,\n Soundshare: 2,\n Priority: 4,\n} as const;\n\n/** Voice session event payloads. */\nexport interface VoiceEvents {\n /** State transition event. */\n stateChange: [VoiceConnectionState, VoiceConnectionState];\n /** Speaking flag event. */\n speaking: [number, number];\n /** Voice transport error event. */\n error: [VoiceError];\n}\n\ntype VoiceListener = (...args: any[]) => unknown;\n\n/** Tracks a Discord voice session and exposes transport-independent lifecycle controls. */\nexport class VoiceConnection {\n /** Current voice connection state. */\n public state: VoiceConnectionState = VoiceConnectionState.Disconnected;\n /** Guild associated with this voice session. */\n public readonly guildId: string;\n /** Voice channel associated with this session. */\n public channelId?: string;\n /** Whether the client is self-muted. */\n public selfMute: boolean;\n /** Whether the client is self-deafened. */\n public selfDeaf: boolean;\n /** Active voice gateway transport, when attached. */\n public gateway?: VoiceGatewayTransport;\n /** Active UDP transport, when attached. */\n public udp?: VoiceUdpTransport;\n /** Registered lifecycle listeners. */\n readonly #listeners = new Map<keyof VoiceEvents, Set<VoiceListener>>();\n /** Receives incoming audio. */\n public readonly receiver: VoiceReceiver;\n\n /** Creates a voice connection state manager. @param options Guild ID or connection options. @throws {TypeError} If the guild ID is empty. */\n public constructor(options: string | VoiceConnectionOptions) {\n const resolved =\n typeof options === \"string\" ? { guildId: options } : options;\n if (!resolved.guildId?.trim()) {\n throw new TypeError(\n \"A guild ID is required for a voice connection.\",\n );\n }\n this.guildId = resolved.guildId;\n this.channelId = resolved.channelId;\n this.selfMute = resolved.selfMute ?? false;\n this.selfDeaf = resolved.selfDeaf ?? false;\n this.receiver = new VoiceReceiver(this);\n }\n\n /** Registers a voice lifecycle listener. @param event Event name. @param listener Listener callback. @returns This connection. @throws {TypeError} If listener is not callable. */\n public on<K extends keyof VoiceEvents>(\n event: K,\n listener: (...args: VoiceEvents[K]) => unknown,\n ): this {\n if (typeof listener !== \"function\")\n throw new TypeError(\"Voice listener must be a function.\");\n let listeners = this.#listeners.get(event);\n if (!listeners) this.#listeners.set(event, (listeners = new Set()));\n listeners.add(listener);\n return this;\n }\n\n /** Removes a voice lifecycle listener. @param event Event name. @param listener Listener callback. @returns This connection. */\n public off<K extends keyof VoiceEvents>(\n event: K,\n listener: (...args: VoiceEvents[K]) => unknown,\n ): this {\n this.#listeners.get(event)?.delete(listener);\n return this;\n }\n\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. */\n public attachTransports(\n gateway: VoiceGatewayTransport,\n udp: VoiceUdpTransport,\n ): this {\n this.assertUsable();\n if (!gateway || !udp)\n throw new TypeError(\n \"Both voice gateway and UDP transports are required.\",\n );\n if (this.gateway || this.udp) this.#closeTransports();\n this.gateway = gateway;\n this.udp = udp;\n // Capture the transport this listener belongs to so that packets from a\n // later-replaced transport (whose listener cannot be unregistered — the\n // VoiceUdpTransport interface exposes no removal API) are ignored.\n const attachedUdp = udp;\n if (attachedUdp.onMessage) {\n attachedUdp.onMessage((packet) => {\n if (this.udp === attachedUdp) this.receiver.onPacket(packet);\n });\n }\n return this;\n }\n\n /** Begins the connection lifecycle. @returns Nothing. @throws {VoiceError} If the connection is destroyed. */\n public connect(): void {\n this.assertUsable();\n if (this.state === VoiceConnectionState.Connected) return;\n this.#transition(VoiceConnectionState.Connecting);\n if (this.state !== VoiceConnectionState.Connecting) return;\n this.#transition(VoiceConnectionState.Connected);\n }\n\n /** Disconnects the current voice session. @returns Nothing. */\n public disconnect(): void {\n if (this.state === VoiceConnectionState.Destroyed) return;\n this.#closeTransports();\n this.receiver.close();\n this.#transition(VoiceConnectionState.Disconnected);\n }\n\n /** Permanently destroys this voice connection. @returns Nothing. */\n public destroy(): void {\n if (this.state === VoiceConnectionState.Destroyed) return;\n this.#closeTransports();\n this.receiver.close();\n this.channelId = undefined;\n this.#transition(VoiceConnectionState.Destroyed);\n this.#listeners.clear();\n }\n\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. */\n public setChannel(channelId: string): void {\n this.assertUsable();\n if (!channelId?.trim())\n throw new TypeError(\"A voice channel ID is required.\");\n this.channelId = channelId;\n }\n\n /** Updates self mute/deaf state. @param options Suppression flags. @returns Nothing. @throws {VoiceError} If the connection is destroyed. */\n public setSuppression(options: {\n selfMute?: boolean;\n selfDeaf?: boolean;\n }): void {\n this.assertUsable();\n if (options.selfMute !== undefined) this.selfMute = options.selfMute;\n if (options.selfDeaf !== undefined) this.selfDeaf = options.selfDeaf;\n }\n\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. */\n public setSpeaking(flags: number = SpeakingFlags.Microphone): void {\n this.assertUsable();\n if (!this.gateway) {\n throw new VoiceError(\n \"A voice gateway transport is required to change speaking state.\",\n );\n }\n this.gateway.send({ op: 5, d: { speaking: flags, delay: 0, ssrc: 0 } });\n this.#emit(\"speaking\", flags, 0);\n }\n\n /** Ensures the connection is still usable. @returns Nothing. @throws {VoiceError} If destroyed. */\n #assertUsable(): void {\n if (this.state === VoiceConnectionState.Destroyed) {\n throw new VoiceError(\"Voice connection has been destroyed.\");\n }\n }\n\n /** Closes both active transports while isolating cleanup failures. @returns Nothing. */\n #closeTransports(): void {\n try {\n this.gateway?.close(1000, \"Voice connection replaced or closed\");\n } catch (error) {\n this.#emit(\n \"error\",\n error instanceof VoiceError\n ? error\n : new VoiceError(\"Voice gateway cleanup failed.\", {\n cause: error,\n }),\n );\n }\n try {\n this.udp?.close();\n } catch (error) {\n this.#emit(\n \"error\",\n error instanceof VoiceError\n ? error\n : new VoiceError(\"Voice UDP cleanup failed.\", {\n cause: error,\n }),\n );\n }\n this.gateway = undefined;\n this.udp = undefined;\n }\n\n /** Transitions to a new lifecycle state and emits the transition event. @param next Next state. @returns Nothing. */\n #transition(next: VoiceConnectionState): void {\n const previous = this.state;\n this.state = next;\n if (previous === next) return;\n for (const listener of this.#listeners.get(\"stateChange\") ?? []) {\n try {\n void listener(next, previous);\n } catch (error) {\n this.#emit(\n \"error\",\n error instanceof VoiceError\n ? error\n : new VoiceError(\"Voice event listener failed.\", {\n cause: error,\n }),\n );\n }\n if (this.state !== next && next !== VoiceConnectionState.Destroyed)\n return;\n }\n }\n\n /** Emits a voice event while isolating consumer failures. @param event Event name. @param args Event arguments. @returns Nothing. */\n #emit<K extends keyof VoiceEvents>(\n event: K,\n ...args: VoiceEvents[K]\n ): void {\n for (const listener of this.#listeners.get(event) ?? []) {\n try {\n void listener(...args);\n } catch {\n /* Consumer failures are isolated. */\n }\n }\n }\n\n /** Ensures the connection is still usable. @returns Nothing. @throws {VoiceError} If destroyed. */\n private assertUsable(): void {\n this.#assertUsable();\n }\n}\n\n// ─── Audio Pipeline ───────────────────────────────────────────────────────────\n\n/** Audio player lifecycle states. */\nexport type AudioPlayerState = \"idle\" | \"playing\" | \"paused\" | \"stopped\";\n\n/** An audio stream — wraps a `ReadableStream<Uint8Array>` with optional metadata. */\nexport class AudioStream {\n /** Underlying binary stream. */ public readonly stream: ReadableStream<Uint8Array>;\n /** Optional human-readable title for the stream. */ public readonly title?: string;\n /** Optional duration hint in seconds. */ public readonly duration?: number;\n\n /** Creates an audio stream.\n * @param stream Binary audio data stream.\n * @param metadata Optional metadata: title and duration in seconds.\n */\n public constructor(\n stream: ReadableStream<Uint8Array>,\n metadata?: { title?: string; duration?: number },\n ) {\n this.stream = stream;\n this.title = metadata?.title;\n this.duration = metadata?.duration;\n }\n}\n\ntype AudioPlayerListener<K extends keyof AudioPlayerEvents> = (\n ...args: AudioPlayerEvents[K]\n) => unknown;\n\ninterface AudioPlayerEvents {\n stateChange: [from: AudioPlayerState, to: AudioPlayerState];\n error: [error: Error];\n finish: [];\n}\n\n/**\n * Lightweight audio player with a simple state machine.\n *\n * States:\n * - `idle` — no stream loaded.\n * - `playing` — actively feeding audio data.\n * - `paused` — temporarily suspended; can be resumed.\n * - `stopped` — stream was stopped and discarded; load a new stream to play again.\n *\n * Events: `stateChange(from, to)`, `error(err)`, `finish()`.\n */\nexport class AudioPlayer {\n #state: AudioPlayerState = \"idle\";\n #stream?: AudioStream;\n #reader?: ReadableStreamDefaultReader<Uint8Array>;\n /** Resolves the pump's pause await when the player is resumed or stopped. */\n #resumeSignal?: () => void;\n readonly #listeners = new Map<\n string,\n Set<AudioPlayerListener<keyof AudioPlayerEvents>>\n >();\n\n /** Current player state. */\n public get state(): AudioPlayerState {\n return this.#state;\n }\n\n /** The audio stream currently loaded, if any. */\n public get current(): AudioStream | undefined {\n return this.#stream;\n }\n\n /**\n * Begins playing an audio stream.\n * If a stream is already playing, it is stopped first; the previous\n * stream's reader cancellation is awaited before the new stream starts so\n * the old and new pump loops cannot run concurrently.\n * @param audioStream Stream to play.\n */\n public async play(audioStream: AudioStream): Promise<void> {\n if (this.#state === \"playing\" || this.#state === \"paused\")\n await this.stop();\n this.#stream = audioStream;\n this.#reader = audioStream.stream.getReader();\n this.#transition(\"playing\");\n this.#pump().catch((err) => this.#emitError(err));\n }\n\n /** Pauses a currently-playing stream. No-op if not playing. */\n public pause(): void {\n if (this.#state !== \"playing\") return;\n this.#transition(\"paused\");\n }\n\n /** Resumes a paused stream. No-op if not paused. */\n public resume(): void {\n if (this.#state !== \"paused\") return;\n this.#transition(\"playing\");\n // Wake the existing pump loop rather than starting a second one.\n const signal = this.#resumeSignal;\n this.#resumeSignal = undefined;\n signal?.();\n }\n\n /**\n * Stops and discards the current stream. The returned promise resolves once\n * the underlying reader's cancellation has settled, so a subsequent `play`\n * never overlaps with the previous pump loop.\n */\n public async stop(): Promise<void> {\n if (this.#state === \"idle\" || this.#state === \"stopped\") return;\n const reader = this.#reader;\n this.#reader = undefined;\n this.#stream = undefined;\n this.#transition(\"stopped\");\n // Release a pump loop that is currently awaiting a resume.\n const signal = this.#resumeSignal;\n this.#resumeSignal = undefined;\n signal?.();\n if (reader) {\n try {\n await reader.cancel();\n } catch {\n /* Cancellation failures are non-fatal. */\n }\n }\n }\n\n /** Registers an event listener. @returns `this` for chaining. */\n public on<K extends keyof AudioPlayerEvents>(\n event: K,\n listener: AudioPlayerListener<K>,\n ): this {\n let set = this.#listeners.get(event) as\n Set<AudioPlayerListener<K>> | undefined;\n if (!set) {\n this.#listeners.set(\n event,\n (set = new Set() as unknown as Set<\n AudioPlayerListener<keyof AudioPlayerEvents>\n >),\n );\n }\n (set as unknown as Set<AudioPlayerListener<K>>).add(listener);\n return this;\n }\n\n /** Removes an event listener. @returns `this` for chaining. */\n public off<K extends keyof AudioPlayerEvents>(\n event: K,\n listener: AudioPlayerListener<K>,\n ): this {\n (\n this.#listeners.get(event) as\n Set<AudioPlayerListener<K>> | undefined\n )?.delete(listener);\n return this;\n }\n\n /**\n * Handles a decoded audio chunk. The base implementation is a no-op;\n * subclasses override this to pipe audio to a UDP socket or other sink.\n * @param chunk A chunk of audio bytes read from the current stream.\n */\n protected onChunk(chunk: Uint8Array): void {\n void chunk;\n }\n\n /** Reads chunks from the reader while the player is in a playing state. */\n async #pump(): Promise<void> {\n const reader = this.#reader;\n if (!reader) return;\n while (true) {\n // Suspend without polling: park until resume()/stop() signals us.\n if (this.#state === \"paused\") {\n await new Promise<void>((resolve) => {\n this.#resumeSignal = resolve;\n });\n }\n // Abort if stopped/idle (e.g. resolved by stop()).\n if (this.#state !== \"playing\") return;\n const { done, value } = await reader.read();\n if (done) {\n if (this.#state === \"playing\" || this.#state === \"paused\") {\n this.#transition(\"idle\");\n this.#stream = undefined;\n this.#reader = undefined;\n this.#emit(\"finish\");\n }\n return;\n }\n // A stop() during the read wins — drop the chunk and exit.\n if (this.#state !== \"playing\" && this.#state !== \"paused\") return;\n // Deliver the chunk to the sink hook; loop back to honour pause state.\n this.onChunk(value);\n }\n }\n\n #transition(next: AudioPlayerState): void {\n if (this.#state === next) return;\n const from = this.#state;\n this.#state = next;\n this.#emit(\"stateChange\", from, next);\n }\n\n #emitError(err: unknown): void {\n this.#emit(\n \"error\",\n err instanceof Error ? err : new VoiceError(String(err)),\n );\n }\n\n #emit<K extends keyof AudioPlayerEvents>(\n event: K,\n ...args: AudioPlayerEvents[K]\n ): void {\n for (const listener of this.#listeners.get(event) ?? []) {\n try {\n void (listener as (...a: unknown[]) => unknown)(...args);\n } catch {\n /* Listener failures are isolated. */\n }\n }\n }\n}\n\n// ─── Audio Receive Pipeline ───────────────────────────────────────────────────\n\n/** Tracks incoming audio and maps SSRCs to readable streams per user. */\nexport class VoiceReceiver {\n /** The connection this receiver is attached to. */\n public readonly connection: VoiceConnection;\n readonly #ssrcMap = new Map<number, string>();\n /** Active stream controllers per user; a Set so multiple subscribers to the\n * same user all receive audio instead of the last one overwriting earlier ones. */\n readonly #streams = new Map<\n string,\n Set<ReadableStreamDefaultController<Uint8Array>>\n >();\n\n /** Creates a VoiceReceiver. @param connection Voice connection. */\n public constructor(connection: VoiceConnection) {\n this.connection = connection;\n }\n\n /** Maps an SSRC to a User ID (usually from gateway speaking events). */\n public mapSsrc(ssrc: number, userId: string): void {\n this.#ssrcMap.set(ssrc, userId);\n }\n\n /** Subscribes to incoming audio from a specific user. @param userId User identifier. @returns A binary readable stream of the audio. */\n public subscribe(userId: string): AudioStream {\n // A destroyed connection never closes its receiver again, so a late\n // subscriber would wait forever.\n if (this.connection.state === VoiceConnectionState.Destroyed)\n throw new VoiceError(\"Voice connection has been destroyed.\");\n let controllerRef: ReadableStreamDefaultController<Uint8Array>;\n const stream = new ReadableStream<Uint8Array>({\n start: (controller) => {\n controllerRef = controller;\n let set = this.#streams.get(userId);\n if (!set) this.#streams.set(userId, (set = new Set()));\n set.add(controller);\n },\n cancel: () => {\n const set = this.#streams.get(userId);\n if (set) {\n set.delete(controllerRef);\n if (set.size === 0) this.#streams.delete(userId);\n }\n },\n });\n return new AudioStream(stream, { title: `User ${userId} Audio` });\n }\n\n /** Ends every subscriber stream and forgets SSRC mappings. Called when the\n * connection disconnects or is destroyed, so readers do not wait forever. */\n public close(): void {\n for (const controllers of this.#streams.values())\n for (const controller of controllers) {\n try {\n controller.close();\n } catch {\n // Already closed or errored by the consumer.\n }\n }\n this.#streams.clear();\n this.#ssrcMap.clear();\n }\n\n /** Processes an incoming UDP packet, extracting SSRC and routing the audio\n * payload (RTP header stripped) to every subscriber for the mapped user. */\n public onPacket(packet: Uint8Array): void {\n if (packet.length < 12) return;\n\n const view = new DataView(\n packet.buffer,\n packet.byteOffset,\n packet.byteLength,\n );\n const ssrc = view.getUint32(8, false);\n\n const userId = this.#ssrcMap.get(ssrc);\n if (!userId) return;\n const controllers = this.#streams.get(userId);\n if (!controllers || controllers.size === 0) return;\n\n // Compute the RTP payload offset: 12-byte fixed header + CSRC list, plus\n // the extension header when the X bit is set.\n const csrcCount = packet[0] & 0x0f;\n let offset = 12 + csrcCount * 4;\n const hasExtension = (packet[0] & 0x10) !== 0;\n if (hasExtension) {\n if (packet.length < offset + 4) return;\n const extWords = view.getUint16(offset + 2, false);\n offset += 4 + extWords * 4;\n }\n if (packet.length < offset) return;\n const payload = packet.slice(offset);\n\n for (const controller of controllers) {\n try {\n controller.enqueue(payload);\n } catch {\n // Ignore errors on closed streams.\n }\n }\n }\n}\n\n// ─── Discord.js-familiar helpers (@discordjs/voice) ───────────────────────────\n\n/** Discord.js-familiar alias for {@link VoiceConnectionState}. Member values are Lunibee's. */\nexport { VoiceConnectionState as VoiceConnectionStatus };\n\n/** Active connections by guild, as tracked by {@link joinVoiceChannel}. */\nconst voiceConnections = new Map<string, VoiceConnection>();\n\n/**\n * Joins (or moves to) a voice channel, reusing the guild's tracked connection.\n * Mirrors `@discordjs/voice`'s `joinVoiceChannel`; transports are still attached\n * with {@link VoiceConnection.attachTransports}.\n * @param options Guild, channel and self mute/deaf flags.\n * @returns The guild's voice connection.\n * @throws {TypeError} If the guild or channel ID is empty.\n */\nexport function joinVoiceChannel(\n options: VoiceConnectionOptions & { channelId: string },\n): VoiceConnection {\n let connection = voiceConnections.get(options.guildId);\n if (!connection) {\n connection = new VoiceConnection(options);\n const created = connection;\n // VoiceConnection emits stateChange as (next, previous).\n created.on(\"stateChange\", (next) => {\n if (\n next === VoiceConnectionState.Destroyed &&\n voiceConnections.get(created.guildId) === created\n )\n voiceConnections.delete(created.guildId);\n });\n voiceConnections.set(options.guildId, connection);\n }\n connection.setChannel(options.channelId);\n connection.setSuppression(options);\n connection.connect();\n return connection;\n}\n\n/** Returns the connection tracked by {@link joinVoiceChannel} for a guild, if any. */\nexport function getVoiceConnection(\n guildId: string,\n): VoiceConnection | undefined {\n return voiceConnections.get(guildId);\n}\n\n/** Creates an {@link AudioPlayer}. Mirrors `@discordjs/voice`'s `createAudioPlayer`. */\nexport function createAudioPlayer(): AudioPlayer {\n return new AudioPlayer();\n}\n\n/** Creates an {@link AudioStream}. Mirrors `@discordjs/voice`'s `createAudioResource`. */\nexport function createAudioResource(\n stream: ReadableStream<Uint8Array>,\n metadata?: { title?: string; duration?: number },\n): AudioStream {\n return new AudioStream(stream, metadata);\n}\n\n/**\n * Resolves once a connection or player reaches `status`, or rejects after `timeoutMs`.\n * Mirrors `@discordjs/voice`'s `entersState`.\n * @returns The same target, for chaining.\n * @throws {VoiceError} If the state is not reached in time.\n */\nexport function entersState<T extends VoiceConnection | AudioPlayer>(\n target: T,\n status: T extends VoiceConnection ? VoiceConnectionState : AudioPlayerState,\n timeoutMs: number,\n): Promise<T> {\n if (target.state === status) return Promise.resolve(target);\n const emitter = target as unknown as {\n on(\n event: \"stateChange\",\n fn: (from: unknown, to: unknown) => void,\n ): unknown;\n off(\n event: \"stateChange\",\n fn: (from: unknown, to: unknown) => void,\n ): unknown;\n };\n // VoiceConnection emits (next, previous); AudioPlayer emits (from, to).\n const isConnection = target instanceof VoiceConnection;\n return new Promise<T>((resolve, reject) => {\n const onChange = (first: unknown, second: unknown): void => {\n if ((isConnection ? first : second) !== status) return;\n clearTimeout(timer);\n emitter.off(\"stateChange\", onChange);\n resolve(target);\n };\n const timer = setTimeout(() => {\n emitter.off(\"stateChange\", onChange);\n reject(\n new VoiceError(\n `Did not enter state \"${String(status)}\" within ${timeoutMs}ms.`,\n ),\n );\n }, timeoutMs);\n emitter.on(\"stateChange\", onChange);\n });\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;AACO,IAAK;AAAA,CAAL,CAAK,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "9E96BE25F2F4C69B64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord Gateway close codes.
|
|
3
|
+
*
|
|
4
|
+
* Discord.js-familiar names and numeric values, matching the Discord Gateway
|
|
5
|
+
* protocol. Exposed so consumers can branch on named codes instead of magic
|
|
6
|
+
* numbers; {@link Gateway} uses them internally to decide resume/identify/stop.
|
|
7
|
+
*/
|
|
8
|
+
export declare const GatewayCloseCodes: {
|
|
9
|
+
readonly UnknownError: 4000;
|
|
10
|
+
readonly UnknownOpcode: 4001;
|
|
11
|
+
readonly DecodeError: 4002;
|
|
12
|
+
readonly NotAuthenticated: 4003;
|
|
13
|
+
readonly AuthenticationFailed: 4004;
|
|
14
|
+
readonly AlreadyAuthenticated: 4005;
|
|
15
|
+
readonly InvalidSeq: 4007;
|
|
16
|
+
readonly RateLimited: 4008;
|
|
17
|
+
readonly SessionTimedOut: 4009;
|
|
18
|
+
readonly InvalidShard: 4010;
|
|
19
|
+
readonly ShardingRequired: 4011;
|
|
20
|
+
readonly InvalidAPIVersion: 4012;
|
|
21
|
+
readonly InvalidIntents: 4013;
|
|
22
|
+
readonly DisallowedIntents: 4014;
|
|
23
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns WebSocket data into complete Gateway text frames.
|
|
3
|
+
*
|
|
4
|
+
* The contract is deliberately boring: bytes in, whole frames out. A decoder
|
|
5
|
+
* knows nothing about opcodes, sessions, heartbeats or JSON validity — it only
|
|
6
|
+
* knows when a frame is *complete*. Parsing the text is the protocol layer's
|
|
7
|
+
* job, so malformed JSON is not a decoding failure; malformed *compressed
|
|
8
|
+
* bytes* are.
|
|
9
|
+
*/
|
|
10
|
+
import { type Inflate } from "node:zlib";
|
|
11
|
+
/** Decodes transport data into complete frames. */
|
|
12
|
+
export interface GatewayDecoder {
|
|
13
|
+
/**
|
|
14
|
+
* Feeds one WebSocket message.
|
|
15
|
+
*
|
|
16
|
+
* A decoder that can answer synchronously must do so: delivery timing is
|
|
17
|
+
* observable to consumers, and an uncompressed frame has no reason to cost
|
|
18
|
+
* a microtask.
|
|
19
|
+
*
|
|
20
|
+
* @returns Every frame completed by this chunk; empty while a frame is
|
|
21
|
+
* still arriving.
|
|
22
|
+
*/
|
|
23
|
+
push(data: unknown): string[] | Promise<string[]>;
|
|
24
|
+
/** Discards buffered state. Call when a connection is replaced. */
|
|
25
|
+
reset(): void;
|
|
26
|
+
}
|
|
27
|
+
/** Passes text frames through unchanged, for an uncompressed connection. */
|
|
28
|
+
export declare class PlainTextDecoder implements GatewayDecoder {
|
|
29
|
+
push(data: unknown): string[];
|
|
30
|
+
reset(): void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Decodes Discord's `zlib-stream` transport compression.
|
|
34
|
+
*
|
|
35
|
+
* Discord sends one zlib stream whose logical payloads are separated by the
|
|
36
|
+
* `Z_SYNC_FLUSH` marker `00 00 FF FF`. A payload may span several WebSocket
|
|
37
|
+
* messages, so output is produced only when that marker arrives: a partial
|
|
38
|
+
* payload is retained rather than guessed at.
|
|
39
|
+
*
|
|
40
|
+
* The inflater carries state across frames, so `push` calls must not
|
|
41
|
+
* interleave. {@link WebSocketTransport} serialises them by construction.
|
|
42
|
+
*/
|
|
43
|
+
export declare class ZlibStreamDecoder implements GatewayDecoder {
|
|
44
|
+
#private;
|
|
45
|
+
/** @param create Inflater factory; injected in tests. */
|
|
46
|
+
constructor(create?: () => Inflate);
|
|
47
|
+
/**
|
|
48
|
+
* A text frame on a compressed connection is already a complete frame, so
|
|
49
|
+
* it is returned synchronously and never touches the inflater. Discord
|
|
50
|
+
* sends everything binary under `zlib-stream`; tolerating a string costs
|
|
51
|
+
* nothing, and treating one as a decode failure — or delaying it by a
|
|
52
|
+
* microtask — would both change observable behaviour.
|
|
53
|
+
*/
|
|
54
|
+
push(data: unknown): string[] | Promise<string[]>;
|
|
55
|
+
reset(): void;
|
|
56
|
+
}
|
|
57
|
+
/** Creates the decoder a connection needs. */
|
|
58
|
+
export declare function createDecoder(compress: boolean | undefined): GatewayDecoder;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heartbeat and liveness for one Gateway connection.
|
|
3
|
+
*
|
|
4
|
+
* Owns every timer that decides whether a connection is alive: the heartbeat
|
|
5
|
+
* interval, the acknowledgement deadline, and the staleness (zombie) watch.
|
|
6
|
+
* It owns no socket, no session, no reconnect policy and no event emission —
|
|
7
|
+
* it reports through callbacks and lets its owner decide what to close.
|
|
8
|
+
*/
|
|
9
|
+
/** Why the connection was declared dead. */
|
|
10
|
+
export type HeartbeatTimeout = {
|
|
11
|
+
/** A heartbeat went unacknowledged within `ackTimeout`. */
|
|
12
|
+
type: "ack";
|
|
13
|
+
/** Time since the unacknowledged heartbeat was sent. */
|
|
14
|
+
elapsedMs: number;
|
|
15
|
+
} | {
|
|
16
|
+
/** No traffic at all arrived within the staleness deadline. */
|
|
17
|
+
type: "zombie";
|
|
18
|
+
/** Time since the last inbound message. */
|
|
19
|
+
silentFor: number;
|
|
20
|
+
/** Deadline that was exceeded. */
|
|
21
|
+
deadline: number;
|
|
22
|
+
};
|
|
23
|
+
/** Collaborators the heartbeat needs from its owner. */
|
|
24
|
+
export interface HeartbeatOptions {
|
|
25
|
+
/** Milliseconds to wait for an ACK before declaring the connection dead. */
|
|
26
|
+
ackTimeout: number;
|
|
27
|
+
/** Minimum silence before the connection is treated as a zombie. */
|
|
28
|
+
zombieTimeout: number;
|
|
29
|
+
/**
|
|
30
|
+
* Writes a heartbeat payload.
|
|
31
|
+
* @returns Whether it reached the wire; `false` is reported as an error.
|
|
32
|
+
*/
|
|
33
|
+
send: (sequence: number | null) => boolean;
|
|
34
|
+
/** Current session sequence, sent as the heartbeat's `d`. */
|
|
35
|
+
sequence: () => number | null;
|
|
36
|
+
/** Whether the underlying connection is open. */
|
|
37
|
+
isConnected: () => boolean;
|
|
38
|
+
/** Called when the connection fails a liveness check. */
|
|
39
|
+
onTimeout: (timeout: HeartbeatTimeout) => void;
|
|
40
|
+
/** Called for non-fatal problems, such as a heartbeat that could not be sent. */
|
|
41
|
+
onError: (error: Error) => void;
|
|
42
|
+
/** Clock injection point for tests. */
|
|
43
|
+
now?: () => number;
|
|
44
|
+
}
|
|
45
|
+
/** Heartbeat lifecycle for one Gateway connection. */
|
|
46
|
+
export declare class GatewayHeartbeat {
|
|
47
|
+
#private;
|
|
48
|
+
constructor(options: HeartbeatOptions);
|
|
49
|
+
/** Round-trip time of the last acknowledged heartbeat, or -1 before the first. */
|
|
50
|
+
get latency(): number;
|
|
51
|
+
/** Heartbeat interval Discord asked for, or 0 before HELLO. */
|
|
52
|
+
get interval(): number;
|
|
53
|
+
/** Whether the most recent heartbeat has been acknowledged. */
|
|
54
|
+
get acknowledged(): boolean;
|
|
55
|
+
/** Milliseconds since the last inbound message, or 0 when none has arrived. */
|
|
56
|
+
get silentFor(): number;
|
|
57
|
+
/**
|
|
58
|
+
* Deadline beyond which silence means the connection is dead.
|
|
59
|
+
*
|
|
60
|
+
* At least one heartbeat interval plus its ACK budget, so a quiet Gateway
|
|
61
|
+
* is never mistaken for a dead one.
|
|
62
|
+
*/
|
|
63
|
+
get stalenessDeadline(): number;
|
|
64
|
+
/** Whether the connection looks alive: last beat acknowledged and traffic recent. */
|
|
65
|
+
isHealthy(): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Begins watching for silence. Call when the connection opens, before HELLO:
|
|
68
|
+
* a connection that never reaches HELLO must still be detected as dead.
|
|
69
|
+
*/
|
|
70
|
+
watch(): void;
|
|
71
|
+
/** Records inbound traffic of any kind. */
|
|
72
|
+
receivedMessage(): void;
|
|
73
|
+
/**
|
|
74
|
+
* Starts heartbeating at Discord's interval.
|
|
75
|
+
*
|
|
76
|
+
* The first beat is jittered across the interval, as Discord's
|
|
77
|
+
* documentation requires, so a fleet reconnecting together does not
|
|
78
|
+
* heartbeat in lockstep.
|
|
79
|
+
*/
|
|
80
|
+
start(interval: number): void;
|
|
81
|
+
/** Stops every timer. Safe to call more than once. */
|
|
82
|
+
stop(): void;
|
|
83
|
+
/** Sends a heartbeat now, and arms the acknowledgement deadline. */
|
|
84
|
+
sendHeartbeat(): void;
|
|
85
|
+
/** Records a heartbeat ACK and updates {@link latency}. */
|
|
86
|
+
acknowledge(): void;
|
|
87
|
+
}
|
package/dist/ws/index.d.ts
CHANGED
|
@@ -1,68 +1,15 @@
|
|
|
1
|
+
import { type SocketFactory } from "./transport.js";
|
|
1
2
|
import { type GatewayPayload, type GatewayProperties, type GatewayPresence, type GatewayIntentResolvable } from "@lunibee/types";
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
readonly InvalidSession: 9;
|
|
13
|
-
readonly Hello: 10;
|
|
14
|
-
readonly HeartbeatAck: 11;
|
|
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
|
-
};
|
|
39
|
-
/** Gateway connection lifecycle states. */
|
|
40
|
-
export declare enum GatewayState {
|
|
41
|
-
/** Initial connection state. */ Connect = "CONNECT",
|
|
42
|
-
/** Gateway HELLO received state. */ Hello = "HELLO",
|
|
43
|
-
/** IDENTIFY operation in progress. */ Identify = "IDENTIFY",
|
|
44
|
-
/** RESUME operation in progress. */ Resume = "RESUME",
|
|
45
|
-
/** Gateway READY state. */ Ready = "READY",
|
|
46
|
-
/** Gateway dispatch processing state. */ Dispatch = "DISPATCH",
|
|
47
|
-
/** Heartbeat processing state. */ Heartbeat = "HEARTBEAT",
|
|
48
|
-
/** Reconnect in progress. */ Reconnect = "RECONNECT",
|
|
49
|
-
/** Gateway is permanently closed. */ Closed = "CLOSED"
|
|
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 };
|
|
61
|
-
/** Gateway protocol error. */
|
|
62
|
-
export declare class GatewayError extends Error {
|
|
63
|
-
/** Gateway close/error code. */ readonly code?: number;
|
|
64
|
-
/** Creates a Gateway error. @param message Error message. @param code Optional Gateway code. @param options Optional error metadata. */ constructor(message: string, code?: number, options?: ErrorOptions);
|
|
65
|
-
}
|
|
3
|
+
export { classifyFrame, classifyPayload, identifyPayload, resumePayload, heartbeatPayload, type GatewayAction, type ProtocolResult, type ProtocolViolation, type IdentifyOptions, type IdentifyProperties, } from "./protocol.js";
|
|
4
|
+
export { WebSocketTransport, type TransportHandlers, type TransportOptions, type SocketFactory, } from "./transport.js";
|
|
5
|
+
export { createDecoder, PlainTextDecoder, ZlibStreamDecoder, type GatewayDecoder, } from "./decoder.js";
|
|
6
|
+
export { GatewayReconnect, classifyCloseCode, FATAL_CLOSE_CODES, IDENTIFY_CLOSE_CODES, type CloseAction, type ReconnectOptions, type ScheduleResult, type ScheduleRefusal, } from "./reconnect.js";
|
|
7
|
+
export { GatewayHeartbeat, type HeartbeatOptions, type HeartbeatTimeout, } from "./heartbeat.js";
|
|
8
|
+
export { GatewaySession, type ResumeInfo, type HandshakeIntent, type InvalidationReason, } from "./session.js";
|
|
9
|
+
export { GatewayOpcodes } from "./opcodes.js";
|
|
10
|
+
export { GatewayCloseCodes } from "./close-codes.js";
|
|
11
|
+
export { GatewayState, Status, GatewayError } from "./state.js";
|
|
12
|
+
import { GatewayState } from "./state.js";
|
|
66
13
|
/** Gateway connection configuration. */
|
|
67
14
|
export interface GatewayOptions {
|
|
68
15
|
/** Authentication token. */ token: string;
|
|
@@ -79,10 +26,16 @@ export interface GatewayOptions {
|
|
|
79
26
|
/** Presence data. */ presence?: GatewayPresence;
|
|
80
27
|
/**
|
|
81
28
|
* Whether to enable zlib-stream transport compression.
|
|
82
|
-
*
|
|
29
|
+
* Decoded with a persistent `node:zlib` inflate stream, matching
|
|
30
|
+
* Discord's zlib-wrapped stream framing (`Z_SYNC_FLUSH` boundaries).
|
|
83
31
|
* When enabled, appends `&compress=zlib-stream` to the Gateway URL.
|
|
84
32
|
*/
|
|
85
33
|
compress?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Constructs the underlying socket. Defaults to the ambient `WebSocket`.
|
|
36
|
+
* Injected to run the Gateway on an alternative transport, or on a stub.
|
|
37
|
+
*/
|
|
38
|
+
createSocket?: SocketFactory;
|
|
86
39
|
}
|
|
87
40
|
/** Gateway event listener. */
|
|
88
41
|
type GatewayListener = (data: unknown) => unknown;
|