diff --git a/packages/discord.js/src/structures/VoiceState.js b/packages/discord.js/src/structures/VoiceState.js index be77df020..af0d1d723 100644 --- a/packages/discord.js/src/structures/VoiceState.js +++ b/packages/discord.js/src/structures/VoiceState.js @@ -205,29 +205,64 @@ class VoiceState extends Base { return this.guild.members.edit(this.id, { channel }, reason); } + /** + * Data to edit the logged in user's own voice state with, when in a stage channel + * @typedef {Object} VoiceStateEditData + * @property {boolean} [requestToSpeak] Whether or not the client is requesting to become a speaker. + * Only available to the logged in user's own voice state. + * @property {boolean} [suppressed] Whether or not the user should be suppressed. + */ + + /** + * Edits this voice state. Currently only available when in a stage channel + * @param {VoiceStateEditData} data The data to edit the voice state with + * @returns {Promise} + */ + async edit(data) { + if (this.channel?.type !== ChannelType.GuildStageVoice) throw new Error('VOICE_NOT_STAGE_CHANNEL'); + + const target = this.client.user.id === this.id ? '@me' : this.id; + + if (target !== '@me' && typeof data.requestToSpeak !== 'undefined') { + throw new Error('VOICE_STATE_NOT_OWN'); + } + + if (!['boolean', 'undefined'].includes(typeof data.requestToSpeak)) { + throw new TypeError('VOICE_STATE_INVALID_TYPE', 'requestToSpeak'); + } + + if (!['boolean', 'undefined'].includes(typeof data.suppressed)) { + throw new TypeError('VOICE_STATE_INVALID_TYPE', 'suppressed'); + } + + await this.client.rest.patch(Routes.guildVoiceState(this.guild.id, target), { + body: { + channel_id: this.channelId, + request_to_speak_timestamp: data.requestToSpeak + ? new Date().toISOString() + : data.requestToSpeak === false + ? null + : undefined, + suppress: data.suppressed, + }, + }); + return this; + } + /** * Toggles the request to speak in the channel. * Only applicable for stage channels and for the client's own voice state. - * @param {boolean} [request=true] Whether or not the client is requesting to become a speaker. + * @param {boolean} [requestToSpeak=true] Whether or not the client is requesting to become a speaker. * @example * // Making the client request to speak in a stage channel (raise its hand) * guild.me.voice.setRequestToSpeak(true); * @example * // Making the client cancel a request to speak * guild.me.voice.setRequestToSpeak(false); - * @returns {Promise} + * @returns {Promise} */ - async setRequestToSpeak(request = true) { - if (this.channel?.type !== ChannelType.GuildStageVoice) throw new Error('VOICE_NOT_STAGE_CHANNEL'); - - if (this.client.user.id !== this.id) throw new Error('VOICE_STATE_NOT_OWN'); - - await this.client.rest.patch(Routes.guildVoiceState(this.guild.id), { - body: { - channel_id: this.channelId, - request_to_speak_timestamp: request ? new Date().toISOString() : null, - }, - }); + setRequestToSpeak(requestToSpeak = true) { + return this.edit({ requestToSpeak }); } /** @@ -245,21 +280,10 @@ class VoiceState extends Base { * @example * // Moving another user to the audience, or cancelling their invite to speak * voiceState.setSuppressed(true); - * @returns {Promise} + * @returns {Promise} */ - async setSuppressed(suppressed = true) { - if (typeof suppressed !== 'boolean') throw new TypeError('VOICE_STATE_INVALID_TYPE', 'suppressed'); - - if (this.channel?.type !== ChannelType.GuildStageVoice) throw new Error('VOICE_NOT_STAGE_CHANNEL'); - - const target = this.client.user.id === this.id ? '@me' : this.id; - - await this.client.rest.patch(Routes.guildVoiceState(this.guild.id, target), { - body: { - channel_id: this.channelId, - suppress: suppressed, - }, - }); + setSuppressed(suppressed = true) { + return this.edit({ suppressed }); } toJSON() { diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 30ab2a843..535521e97 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -2517,8 +2517,9 @@ export class VoiceState extends Base { public setMute(mute?: boolean, reason?: string): Promise; public disconnect(reason?: string): Promise; public setChannel(channel: GuildVoiceChannelResolvable | null, reason?: string): Promise; - public setRequestToSpeak(request?: boolean): Promise; - public setSuppressed(suppressed?: boolean): Promise; + public setRequestToSpeak(request?: boolean): Promise; + public setSuppressed(suppressed?: boolean): Promise; + public edit(data: VoiceStateEditData): Promise; } export class Webhook extends WebhookMixin() { @@ -5129,6 +5130,11 @@ export type VoiceBasedChannelTypes = VoiceBasedChannel['type']; export type VoiceChannelResolvable = Snowflake | VoiceChannel; +export interface VoiceStateEditData { + requestToSpeak?: boolean; + suppressed?: boolean; +} + export type WebhookClientData = WebhookClientDataIdWithToken | WebhookClientDataURL; export interface WebhookClientDataIdWithToken {