From 6e61ec635858d70c56f1d200d0575560f879b344 Mon Sep 17 00:00:00 2001 From: Danial Raza Date: Tue, 28 Jan 2025 13:33:34 +0100 Subject: [PATCH] feat(Client): add request soundboard sounds (#10608) * feat(Client): add request soundboard sounds * docs: add `guildId` to example Co-authored-by: Almeida * refactor: remove `groupBy` polyfill --------- Co-authored-by: Almeida Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- packages/core/src/client.ts | 121 ++++++++++++++++++++++++++++++++---- 1 file changed, 110 insertions(+), 11 deletions(-) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 5bb1c96b7..1253a36ef 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -81,6 +81,8 @@ import { type GatewayVoiceStateUpdateData, type GatewayVoiceStateUpdateDispatchData, type GatewayWebhooksUpdateDispatchData, + type GatewayRequestSoundboardSoundsData, + type GatewaySoundboardSoundsDispatchData, } from 'discord-api-types/v10'; import type { Gateway } from './Gateway.js'; import { API } from './api/index.js'; @@ -142,6 +144,7 @@ export interface MappedEvents { [GatewayDispatchEvents.GuildSoundboardSoundDelete]: [ToEventProps]; [GatewayDispatchEvents.GuildSoundboardSoundUpdate]: [ToEventProps]; [GatewayDispatchEvents.GuildSoundboardSoundsUpdate]: [ToEventProps]; + [GatewayDispatchEvents.SoundboardSounds]: [ToEventProps]; [GatewayDispatchEvents.GuildStickersUpdate]: [ToEventProps]; [GatewayDispatchEvents.GuildUpdate]: [ToEventProps]; [GatewayDispatchEvents.IntegrationCreate]: [ToEventProps]; @@ -196,6 +199,10 @@ export interface RequestGuildMembersResult { presences: NonNullable; } +function createTimer(controller: AbortController, timeout: number) { + return setTimeout(() => controller.abort(), timeout); +} + export class Client extends AsyncEventEmitter { public readonly rest: REST; @@ -234,12 +241,7 @@ export class Client extends AsyncEventEmitter { const controller = new AbortController(); - const createTimer = () => - setTimeout(() => { - controller.abort(); - }, timeout); - - let timer: NodeJS.Timeout | undefined = createTimer(); + let timer: NodeJS.Timeout | undefined = createTimer(controller, timeout); await this.gateway.send(shardId, { op: GatewayOpcodes.RequestGuildMembers, @@ -270,11 +272,9 @@ export class Client extends AsyncEventEmitter { chunkCount: data.chunk_count, }; - if (data.chunk_index >= data.chunk_count - 1) { - break; - } else { - timer = createTimer(); - } + if (data.chunk_index >= data.chunk_count - 1) break; + + timer = createTimer(controller, timeout); } } catch (error) { if (error instanceof Error && error.name === 'AbortError') { @@ -316,6 +316,105 @@ export class Client extends AsyncEventEmitter { return { members, nonce, notFound, presences }; } + /** + * Requests soundboard sounds from the gateway and returns an async iterator that yields the data from each soundboard sounds event. + * + * @see {@link https://discord.com/developers/docs/topics/gateway-events#request-soundboard-sounds} + * @param options - The options for the request + * @param timeout - The timeout for waiting for each soundboard sounds + * @example + * Requesting soundboard sounds for specific guilds + * ```ts + * for await (const { guildId, soundboardSounds } of this.requestSoundboardSoundsIterator({ + * guild_ids: ['1234567890', '9876543210'], + * })) { + * console.log(`Soundboard sounds for guild ${guildId}:`, soundboardSounds); + * } + * ``` + */ + public async *requestSoundboardSoundsIterator(options: GatewayRequestSoundboardSoundsData, timeout = 10_000) { + const shardCount = await this.gateway.getShardCount(); + const shardIds = Map.groupBy(options.guild_ids, (guildId) => calculateShardId(guildId, shardCount)); + + const controller = new AbortController(); + + let timer: NodeJS.Timeout | undefined = createTimer(controller, timeout); + + for (const [shardId, guildIds] of shardIds) { + await this.gateway.send(shardId, { + op: GatewayOpcodes.RequestSoundboardSounds, + // eslint-disable-next-line id-length + d: { + ...options, + guild_ids: guildIds, + }, + }); + } + + try { + const iterator = AsyncEventEmitter.on(this, GatewayDispatchEvents.SoundboardSounds, { + signal: controller.signal, + }); + + const guildIds = new Set(options.guild_ids); + + for await (const [{ data }] of iterator) { + if (!guildIds.has(data.guild_id)) continue; + + clearTimeout(timer); + timer = undefined; + + yield { + guildId: data.guild_id, + soundboardSounds: data.soundboard_sounds, + }; + + guildIds.delete(data.guild_id); + + if (guildIds.size === 0) break; + + timer = createTimer(controller, timeout); + } + } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + throw new Error('Request timed out'); + } + + throw error; + } finally { + if (timer) { + clearTimeout(timer); + } + } + } + + /** + * Requests soundboard sounds from the gateway. + * + * @see {@link https://discord.com/developers/docs/topics/gateway-events#request-soundboard-sounds} + * @param options - The options for the request + * @param timeout - The timeout for waiting for each soundboard sounds event + * @example + * Requesting soundboard sounds for specific guilds + * ```ts + * const soundboardSounds = await client.requestSoundboardSounds({ guild_ids: ['1234567890', '9876543210'], }); + * + * console.log(soundboardSounds.get('1234567890')); + * ``` + */ + public async requestSoundboardSounds(options: GatewayRequestSoundboardSoundsData, timeout = 10_000) { + const soundboardSounds = new Map< + GatewaySoundboardSoundsDispatchData['guild_id'], + GatewaySoundboardSoundsDispatchData['soundboard_sounds'] + >(); + + for await (const data of this.requestSoundboardSoundsIterator(options, timeout)) { + soundboardSounds.set(data.guildId, data.soundboardSounds); + } + + return soundboardSounds; + } + /** * Updates the voice state of the bot user *