diff --git a/packages/core/src/api/guild.ts b/packages/core/src/api/guild.ts index 697db33abcc4..1963b4b7a153 100644 --- a/packages/core/src/api/guild.ts +++ b/packages/core/src/api/guild.ts @@ -67,7 +67,6 @@ import { type RESTPatchAPIGuildTemplateJSONBody, type RESTPatchAPIGuildTemplateResult, type RESTPatchAPIGuildVoiceStateCurrentMemberJSONBody, - type RESTPatchAPIGuildVoiceStateCurrentMemberResult, type RESTPatchAPIGuildVoiceStateUserJSONBody, type RESTPatchAPIGuildWelcomeScreenJSONBody, type RESTPatchAPIGuildWelcomeScreenResult, @@ -102,6 +101,7 @@ import { type RESTPutAPIGuildTemplateSyncResult, type Snowflake, } from 'discord-api-types/v10'; +import { VoiceAPI } from './voice'; export class GuildsAPI { public constructor(private readonly rest: REST) {} @@ -687,11 +687,12 @@ export class GuildsAPI { /** * Edits a user's voice state in a guild * - * @see {@link https://discord.com/developers/docs/resources/guild#modify-user-voice-state} + * @see {@link https://discord.com/developers/docs/resources/voice#modify-user-voice-state} * @param guildId - The id of the guild to edit the current user's voice state in * @param userId - The id of the user to edit the voice state for * @param body - The data for editing the voice state * @param options - The options for editing the voice state + * @deprecated Use {@link VoiceAPI.editUserVoiceState} instead */ public async editUserVoiceState( guildId: Snowflake, @@ -699,7 +700,7 @@ export class GuildsAPI { body: RESTPatchAPIGuildVoiceStateUserJSONBody, { reason, signal }: Pick = {}, ) { - await this.rest.patch(Routes.guildVoiceState(guildId, userId), { reason, body, signal }); + return new VoiceAPI(this.rest).editUserVoiceState(guildId, userId, body, { reason, signal }); } /** @@ -1298,14 +1299,18 @@ export class GuildsAPI { /** * Sets the voice state for the current user * - * @see {@link https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state} + * @see {@link https://discord.com/developers/docs/resources/voice#modify-current-user-voice-state} * @param guildId - The id of the guild - * @param body - The options for setting the voice state + * @param body - The data for setting the voice state + * @param options - The options for setting the voice state + * @deprecated Use {@link VoiceAPI.editVoiceState} instead */ - public async setVoiceState(guildId: Snowflake, body: RESTPatchAPIGuildVoiceStateCurrentMemberJSONBody = {}) { - return this.rest.patch(Routes.guildVoiceState(guildId, '@me'), { - body, - }) as Promise; + public async setVoiceState( + guildId: Snowflake, + body: RESTPatchAPIGuildVoiceStateCurrentMemberJSONBody = {}, + { signal }: Pick = {}, + ) { + return new VoiceAPI(this.rest).editVoiceState(guildId, body, { signal }); } /** diff --git a/packages/core/src/api/voice.ts b/packages/core/src/api/voice.ts index a1ff6869bd51..d58e8069a90f 100644 --- a/packages/core/src/api/voice.ts +++ b/packages/core/src/api/voice.ts @@ -1,7 +1,17 @@ /* eslint-disable jsdoc/check-param-names */ import type { RequestData, REST } from '@discordjs/rest'; -import { Routes, type RESTGetAPIVoiceRegionsResult } from 'discord-api-types/v10'; +import { + Routes, + type Snowflake, + type RESTGetAPIVoiceRegionsResult, + type RESTGetAPIGuildVoiceStateUserResult, + type RESTGetAPIGuildVoiceStateCurrentMemberResult, + type RESTPatchAPIGuildVoiceStateUserJSONBody, + type RESTPatchAPIGuildVoiceStateCurrentMemberResult, + type RESTPatchAPIGuildVoiceStateCurrentMemberJSONBody, + type RESTPatchAPIGuildVoiceStateUserResult, +} from 'discord-api-types/v10'; export class VoiceAPI { public constructor(private readonly rest: REST) {} @@ -15,4 +25,69 @@ export class VoiceAPI { public async getVoiceRegions({ signal }: Pick = {}) { return this.rest.get(Routes.voiceRegions(), { signal }) as Promise; } + + /** + * Fetches voice state of a user by their id + * + * @see {@link https://discord.com/developers/docs/resources/voice#get-user-voice-state} + * @param options - The options for fetching user voice state + */ + public async getUserVoiceState(guildId: Snowflake, userId: Snowflake, { signal }: Pick = {}) { + return this.rest.get(Routes.guildVoiceState(guildId, userId), { + signal, + }) as Promise; + } + + /** + * Fetches the current user's voice state + * + * @see {@link https://discord.com/developers/docs/resources/voice#get-current-user-voice-state} + * @param options - The options for fetching user voice state + */ + public async getVoiceState(guildId: Snowflake, { signal }: Pick = {}) { + return this.rest.get(Routes.guildVoiceState(guildId, '@me'), { + signal, + }) as Promise; + } + + /** + * Edits a user's voice state in a guild + * + * @see {@link https://discord.com/developers/docs/resources/voice#modify-user-voice-state} + * @param guildId - The id of the guild to edit the current user's voice state in + * @param userId - The id of the user to edit the voice state for + * @param body - The data for editing the voice state + * @param options - The options for editing the voice state + */ + public async editUserVoiceState( + guildId: Snowflake, + userId: Snowflake, + body: RESTPatchAPIGuildVoiceStateUserJSONBody, + { reason, signal }: Pick = {}, + ) { + return this.rest.patch(Routes.guildVoiceState(guildId, userId), { + reason, + body, + signal, + }) as Promise; + } + + /** + * Edits the voice state for the current user + * + * @see {@link https://discord.com/developers/docs/resources/voice#modify-current-user-voice-state} + * @param guildId - The id of the guild + * @param body - The data for editing the voice state + * @param options - The options for editing the voice state + */ + public async editVoiceState( + guildId: Snowflake, + body: RESTPatchAPIGuildVoiceStateCurrentMemberJSONBody = {}, + { signal }: Pick = {}, + ) { + return this.rest.patch(Routes.guildVoiceState(guildId, '@me'), { + body, + signal, + }) as Promise; + } } diff --git a/packages/discord.js/src/managers/VoiceStateManager.js b/packages/discord.js/src/managers/VoiceStateManager.js index c42fdd2c36e7..586bd5ad9e08 100644 --- a/packages/discord.js/src/managers/VoiceStateManager.js +++ b/packages/discord.js/src/managers/VoiceStateManager.js @@ -1,5 +1,6 @@ 'use strict'; +const { Routes } = require('discord-api-types/v10'); const CachedManager = require('./CachedManager'); const VoiceState = require('../structures/VoiceState'); @@ -32,6 +33,27 @@ class VoiceStateManager extends CachedManager { if (cache) this.cache.set(data.user_id, entry); return entry; } + + /** + * Obtains a user's voice state from discord or from the cache if it's already available. + * @param {GuildMemberResolvable|'@me'} member The member whose voice state is to be fetched + * @param {BaseFetchOptions} [options] Additional options for this fetch + * @returns {Promise} + * @example + * // Fetch a member's voice state + * guild.voiceStates.fetch("66564597481480192") + * .then(console.log) + * .catch(console.error); + */ + async fetch(member, { cache = true, force = false } = {}) { + const id = member === '@me' ? member : this.guild.members.resolveId(member); + if (!force) { + const existing = this.cache.get(id === '@me' ? this.client.user.id : id); + if (existing) return existing; + } + const data = await this.client.rest.get(Routes.guildVoiceState(this.guild.id, id)); + return this._add(data, cache); + } } module.exports = VoiceStateManager; diff --git a/packages/discord.js/src/structures/VoiceState.js b/packages/discord.js/src/structures/VoiceState.js index 8f379c5a7c2d..890681e53a0e 100644 --- a/packages/discord.js/src/structures/VoiceState.js +++ b/packages/discord.js/src/structures/VoiceState.js @@ -250,6 +250,15 @@ class VoiceState extends Base { return this; } + /** + * Fetches this voice state. + * @param {boolean} [force=true] Whether to skip the cache check and request the API + * @returns {Promise} + */ + fetch(force = true) { + return this.guild.voiceStates.fetch(this.id, { force }); + } + /** * Toggles the request to speak in the channel. * Only applicable for stage channels and for the client's own voice state. diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 7a7231c497b3..c522050619fd 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -3625,6 +3625,7 @@ export class VoiceState extends Base { public setRequestToSpeak(request?: boolean): Promise; public setSuppressed(suppressed?: boolean): Promise; public edit(options: VoiceStateEditOptions): Promise; + public fetch(force?: boolean): Promise; } // tslint:disable-next-line no-empty-interface @@ -4627,6 +4628,7 @@ export class UserManager extends CachedManager export class VoiceStateManager extends CachedManager { private constructor(guild: Guild, iterable?: Iterable); public guild: Guild; + public fetch(member: GuildMemberResolvable | '@me', options?: BaseFetchOptions): Promise; } //#endregion diff --git a/packages/discord.js/typings/rawDataTypes.d.ts b/packages/discord.js/typings/rawDataTypes.d.ts index 1113ee6883e6..794daafa4069 100644 --- a/packages/discord.js/typings/rawDataTypes.d.ts +++ b/packages/discord.js/typings/rawDataTypes.d.ts @@ -47,6 +47,7 @@ import { APIUnavailableGuild, APIUser, APIVoiceRegion, + APIVoiceState, APIWebhook, GatewayActivity, GatewayActivityAssets, @@ -62,7 +63,6 @@ import { GatewayPresenceUpdate, GatewayReadyDispatchData, GatewayTypingStartDispatchData, - GatewayVoiceState, RESTAPIPartialCurrentUserGuild, RESTGetAPIWebhookWithTokenResult, RESTPatchAPIChannelMessageJSONBody, @@ -195,7 +195,7 @@ export type RawUserData = export type RawVoiceRegionData = APIVoiceRegion; -export type RawVoiceStateData = GatewayVoiceState | Omit; +export type RawVoiceStateData = APIVoiceState | Omit; export type RawWebhookData = | APIWebhook