diff --git a/src/client/actions/WebhooksUpdate.js b/src/client/actions/WebhooksUpdate.js index b23fe23b277d..99259bacdc73 100644 --- a/src/client/actions/WebhooksUpdate.js +++ b/src/client/actions/WebhooksUpdate.js @@ -10,7 +10,8 @@ class WebhooksUpdate extends Action { /** * Emitted whenever a channel has its webhooks changed. * @event Client#webhookUpdate - * @param {TextChannel|NewsChannel|VoiceChannel|ForumChannel} channel The channel that had a webhook update + * @param {TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel} channel + * The channel that had a webhook update */ if (channel) client.emit(Events.WEBHOOKS_UPDATE, channel); } diff --git a/src/structures/BaseGuildVoiceChannel.js b/src/structures/BaseGuildVoiceChannel.js index 7caa79d9bda5..87b3c3b9ab3d 100644 --- a/src/structures/BaseGuildVoiceChannel.js +++ b/src/structures/BaseGuildVoiceChannel.js @@ -2,16 +2,45 @@ const { Collection } = require('@discordjs/collection'); const GuildChannel = require('./GuildChannel'); +const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const MessageManager = require('../managers/MessageManager'); +const { VideoQualityModes } = require('../util/Constants'); const Permissions = require('../util/Permissions'); /** * Represents a voice-based guild channel on Discord. * @extends {GuildChannel} + * @implements {TextBasedChannel} */ class BaseGuildVoiceChannel extends GuildChannel { + constructor(guild, data, client) { + super(guild, data, client, false); + /** + * A manager of the messages sent to this channel + * @type {MessageManager} + */ + this.messages = new MessageManager(this); + + /** + * If the guild considers this channel NSFW + * @type {boolean} + */ + this.nsfw = Boolean(data.nsfw); + + this._patch(data); + } + _patch(data) { super._patch(data); + if ('bitrate' in data) { + /** + * The bitrate of this voice-based channel + * @type {number} + */ + this.bitrate = data.bitrate; + } + if ('rtc_region' in data) { /** * The RTC region for this voice-based channel. This region is automatically selected if `null`. @@ -20,20 +49,46 @@ class BaseGuildVoiceChannel extends GuildChannel { this.rtcRegion = data.rtc_region; } - if ('bitrate' in data) { + if ('user_limit' in data) { /** - * The bitrate of this voice-based channel + * The maximum amount of users allowed in this channel. * @type {number} */ - this.bitrate = data.bitrate; + this.userLimit = data.user_limit; } - if ('user_limit' in data) { + if ('video_quality_mode' in data) { /** - * The maximum amount of users allowed in this channel. + * The camera video quality mode of the channel. + * @type {?VideoQualityMode} + */ + this.videoQualityMode = VideoQualityModes[data.video_quality_mode]; + } else { + this.videoQualityMode ??= null; + } + + if ('last_message_id' in data) { + /** + * The last message id sent in the channel, if one was sent + * @type {?Snowflake} + */ + this.lastMessageId = data.last_message_id; + } + + if ('messages' in data) { + for (const message of data.messages) this.messages._add(message); + } + + if ('rate_limit_per_user' in data) { + /** + * The rate limit per user (slowmode) for this channel in seconds * @type {number} */ - this.userLimit = data.user_limit; + this.rateLimitPerUser = data.rate_limit_per_user; + } + + if ('nsfw' in data) { + this.nsfw = data.nsfw; } } @@ -80,6 +135,45 @@ class BaseGuildVoiceChannel extends GuildChannel { ); } + /** + * Creates an invite to this guild channel. + * @param {CreateInviteOptions} [options={}] The options for creating the invite + * @returns {Promise} + * @example + * // Create an invite to a channel + * channel.createInvite() + * .then(invite => console.log(`Created an invite with a code of ${invite.code}`)) + * .catch(console.error); + */ + createInvite(options) { + return this.guild.invites.create(this.id, options); + } + + /** + * Fetches a collection of invites to this guild channel. + * Resolves with a collection mapping invites by their codes. + * @param {boolean} [cache=true] Whether or not to cache the fetched invites + * @returns {Promise>} + */ + fetchInvites(cache = true) { + return this.guild.invites.fetch({ channelId: this.id, cache }); + } + + /** + * Sets the bitrate of the channel. + * @param {number} bitrate The new bitrate + * @param {string} [reason] Reason for changing the channel's bitrate + * @returns {Promise} + * @example + * // Set the bitrate of a voice channel + * channel.setBitrate(48_000) + * .then(channel => console.log(`Set bitrate to ${channel.bitrate}bps for ${channel.name}`)) + * .catch(console.error); + */ + setBitrate(bitrate, reason) { + return this.edit({ bitrate }, reason); + } + /** * Sets the RTC region of the channel. * @param {?string} rtcRegion The new region of the channel. Set to `null` to remove a specific region for the channel @@ -97,28 +191,46 @@ class BaseGuildVoiceChannel extends GuildChannel { } /** - * Creates an invite to this guild channel. - * @param {CreateInviteOptions} [options={}] The options for creating the invite - * @returns {Promise} + * Sets the user limit of the channel. + * @param {number} userLimit The new user limit + * @param {string} [reason] Reason for changing the user limit + * @returns {Promise} * @example - * // Create an invite to a channel - * channel.createInvite() - * .then(invite => console.log(`Created an invite with a code of ${invite.code}`)) + * // Set the user limit of a voice channel + * channel.setUserLimit(42) + * .then(channel => console.log(`Set user limit to ${channel.userLimit} for ${channel.name}`)) * .catch(console.error); */ - createInvite(options) { - return this.guild.invites.create(this.id, options); + setUserLimit(userLimit, reason) { + return this.edit({ userLimit }, reason); } /** - * Fetches a collection of invites to this guild channel. - * Resolves with a collection mapping invites by their codes. - * @param {boolean} [cache=true] Whether or not to cache the fetched invites - * @returns {Promise>} + * Sets the camera video quality mode of the channel. + * @param {VideoQualityMode|number} videoQualityMode The new camera video quality mode. + * @param {string} [reason] Reason for changing the camera video quality mode. + * @returns {Promise} */ - fetchInvites(cache = true) { - return this.guild.invites.fetch({ channelId: this.id, cache }); + setVideoQualityMode(videoQualityMode, reason) { + return this.edit({ videoQualityMode }, reason); } + + // These are here only for documentation purposes - they are implemented by TextBasedChannel + /* eslint-disable no-empty-function */ + get lastMessage() {} + send() {} + sendTyping() {} + createMessageCollector() {} + awaitMessages() {} + createMessageComponentCollector() {} + awaitMessageComponent() {} + bulkDelete() {} + fetchWebhooks() {} + createWebhook() {} + setRateLimitPerUser() {} + setNSFW() {} } +TextBasedChannel.applyToClass(BaseGuildVoiceChannel, true, ['lastPinAt']); + module.exports = BaseGuildVoiceChannel; diff --git a/src/structures/StageChannel.js b/src/structures/StageChannel.js index b57beb5cb228..f7a2d8f0072f 100644 --- a/src/structures/StageChannel.js +++ b/src/structures/StageChannel.js @@ -52,6 +52,19 @@ class StageChannel extends BaseGuildVoiceChannel { return this.edit({ topic }, reason); } + /** + * Sets the bitrate of the channel. + * @name StageChannel#setBitrate + * @param {number} bitrate The new bitrate + * @param {string} [reason] Reason for changing the channel's bitrate + * @returns {Promise} + * @example + * // Set the bitrate of a voice channel + * stageChannel.setBitrate(48_000) + * .then(channel => console.log(`Set bitrate to ${channel.bitrate}bps for ${channel.name}`)) + * .catch(console.error); + */ + /** * Sets the RTC region of the channel. * @name StageChannel#setRTCRegion @@ -65,6 +78,27 @@ class StageChannel extends BaseGuildVoiceChannel { * // Remove a fixed region for this channel - let Discord decide automatically * stageChannel.setRTCRegion(null, 'We want to let Discord decide.'); */ + + /** + * Sets the user limit of the channel. + * @name StageChannel#setUserLimit + * @param {number} userLimit The new user limit + * @param {string} [reason] Reason for changing the user limit + * @returns {Promise} + * @example + * // Set the user limit of a voice channel + * stageChannel.setUserLimit(42) + * .then(channel => console.log(`Set user limit to ${channel.userLimit} for ${channel.name}`)) + * .catch(console.error); + */ + + /** + * Sets the camera video quality mode of the channel. + * @name StageChannel#setVideoQualityMode + * @param {VideoQualityMode|number} videoQualityMode The new camera video quality mode. + * @param {string} [reason] Reason for changing the camera video quality mode. + * @returns {Promise} + */ } module.exports = StageChannel; diff --git a/src/structures/VoiceChannel.js b/src/structures/VoiceChannel.js index bdb1f56bfaa0..e2bc57965ce7 100644 --- a/src/structures/VoiceChannel.js +++ b/src/structures/VoiceChannel.js @@ -2,9 +2,6 @@ const process = require('node:process'); const BaseGuildVoiceChannel = require('./BaseGuildVoiceChannel'); -const TextBasedChannel = require('./interfaces/TextBasedChannel'); -const MessageManager = require('../managers/MessageManager'); -const { VideoQualityModes } = require('../util/Constants'); const Permissions = require('../util/Permissions'); let deprecationEmittedForEditable = false; @@ -12,65 +9,8 @@ let deprecationEmittedForEditable = false; /** * Represents a guild voice channel on Discord. * @extends {BaseGuildVoiceChannel} - * @implements {TextBasedChannel} */ class VoiceChannel extends BaseGuildVoiceChannel { - constructor(guild, data, client) { - super(guild, data, client, false); - - /** - * A manager of the messages sent to this channel - * @type {MessageManager} - */ - this.messages = new MessageManager(this); - - /** - * If the guild considers this channel NSFW - * @type {boolean} - */ - this.nsfw = Boolean(data.nsfw); - - this._patch(data); - } - - _patch(data) { - super._patch(data); - - if ('video_quality_mode' in data) { - /** - * The camera video quality mode of the channel. - * @type {?VideoQualityMode} - */ - this.videoQualityMode = VideoQualityModes[data.video_quality_mode]; - } else { - this.videoQualityMode ??= null; - } - - if ('last_message_id' in data) { - /** - * The last message id sent in the channel, if one was sent - * @type {?Snowflake} - */ - this.lastMessageId = data.last_message_id; - } - - if ('messages' in data) { - for (const message of data.messages) this.messages._add(message); - } - - if ('rate_limit_per_user' in data) { - /** - * The rate limit per user (slowmode) for this channel in seconds - * @type {number} - */ - this.rateLimitPerUser = data.rate_limit_per_user; - } - - if ('nsfw' in data) { - this.nsfw = data.nsfw; - } - } - /** * Whether the channel is editable by the client user * @type {boolean} @@ -120,74 +60,51 @@ class VoiceChannel extends BaseGuildVoiceChannel { /** * Sets the bitrate of the channel. + * @name VoiceChannel#setBitrate * @param {number} bitrate The new bitrate * @param {string} [reason] Reason for changing the channel's bitrate * @returns {Promise} * @example * // Set the bitrate of a voice channel * voiceChannel.setBitrate(48_000) - * .then(vc => console.log(`Set bitrate to ${vc.bitrate}bps for ${vc.name}`)) + * .then(channel => console.log(`Set bitrate to ${channel.bitrate}bps for ${channel.name}`)) * .catch(console.error); */ - setBitrate(bitrate, reason) { - return this.edit({ bitrate }, reason); - } + + /** + * Sets the RTC region of the channel. + * @name VoiceChannel#setRTCRegion + * @param {?string} rtcRegion The new region of the channel. Set to `null` to remove a specific region for the channel + * @param {string} [reason] The reason for modifying this region. + * @returns {Promise} + * @example + * // Set the RTC region to sydney + * voiceChannel.setRTCRegion('sydney'); + * @example + * // Remove a fixed region for this channel - let Discord decide automatically + * voiceChannel.setRTCRegion(null, 'We want to let Discord decide.'); + */ /** * Sets the user limit of the channel. + * @name VoiceChannel#setUserLimit * @param {number} userLimit The new user limit * @param {string} [reason] Reason for changing the user limit * @returns {Promise} * @example * // Set the user limit of a voice channel * voiceChannel.setUserLimit(42) - * .then(vc => console.log(`Set user limit to ${vc.userLimit} for ${vc.name}`)) + * .then(channel => console.log(`Set user limit to ${channel.userLimit} for ${channel.name}`)) * .catch(console.error); */ - setUserLimit(userLimit, reason) { - return this.edit({ userLimit }, reason); - } /** * Sets the camera video quality mode of the channel. + * @name VoiceChannel#setVideoQualityMode * @param {VideoQualityMode|number} videoQualityMode The new camera video quality mode. * @param {string} [reason] Reason for changing the camera video quality mode. * @returns {Promise} */ - setVideoQualityMode(videoQualityMode, reason) { - return this.edit({ videoQualityMode }, reason); - } - - // These are here only for documentation purposes - they are implemented by TextBasedChannel - /* eslint-disable no-empty-function */ - get lastMessage() {} - send() {} - sendTyping() {} - createMessageCollector() {} - awaitMessages() {} - createMessageComponentCollector() {} - awaitMessageComponent() {} - bulkDelete() {} - fetchWebhooks() {} - createWebhook() {} - setRateLimitPerUser() {} - setNSFW() {} - - /** - * Sets the RTC region of the channel. - * @name VoiceChannel#setRTCRegion - * @param {?string} rtcRegion The new region of the channel. Set to `null` to remove a specific region for the channel - * @param {string} [reason] The reason for modifying this region. - * @returns {Promise} - * @example - * // Set the RTC region to sydney - * voiceChannel.setRTCRegion('sydney'); - * @example - * // Remove a fixed region for this channel - let Discord decide automatically - * voiceChannel.setRTCRegion(null, 'We want to let Discord decide.'); - */ } -TextBasedChannel.applyToClass(VoiceChannel, true, ['lastPinAt']); - module.exports = VoiceChannel; diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 0efb077fa729..22b86812b6d8 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -237,7 +237,8 @@ class Webhook { * @typedef {Object} WebhookEditData * @property {string} [name=this.name] The new name for the webhook * @property {?(BufferResolvable)} [avatar] The new avatar for the webhook - * @property {GuildTextChannelResolvable} [channel] The new channel for the webhook + * @property {GuildTextChannelResolvable|VoiceChannel|StageChannel|ForumChannel} [channel] + * The new channel for the webhook */ /** diff --git a/src/util/Constants.js b/src/util/Constants.js index 6129da41713a..8d7d04dd8e86 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -712,7 +712,8 @@ exports.ChannelTypes = createEnum([ * * NewsChannel * * ThreadChannel * * VoiceChannel - * @typedef {DMChannel|TextChannel|NewsChannel|ThreadChannel|VoiceChannel} TextBasedChannels + * * StageChannel + * @typedef {DMChannel|TextChannel|NewsChannel|ThreadChannel|VoiceChannel|StageChannel} TextBasedChannels */ /** @@ -731,6 +732,7 @@ exports.ChannelTypes = createEnum([ * * GUILD_PUBLIC_THREAD * * GUILD_PRIVATE_THREAD * * GUILD_VOICE + * * GUILD_STAGE_VOICE * @typedef {string} TextBasedChannelTypes */ exports.TextBasedChannelTypes = [ @@ -741,6 +743,7 @@ exports.TextBasedChannelTypes = [ 'GUILD_PUBLIC_THREAD', 'GUILD_PRIVATE_THREAD', 'GUILD_VOICE', + 'GUILD_STAGE_VOICE', ]; /** diff --git a/typings/index.d.ts b/typings/index.d.ts index ab7e2a25d60f..50d2cee2dad8 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -479,17 +479,23 @@ export class BaseGuildTextChannel extends TextBasedChannelMixin(GuildChannel) { public setType(type: Pick, reason?: string): Promise; } -export class BaseGuildVoiceChannel extends GuildChannel { +export class BaseGuildVoiceChannel extends TextBasedChannelMixin(GuildChannel, ['lastPinTimestamp', 'lastPinAt']) { public constructor(guild: Guild, data?: RawGuildChannelData); public readonly members: Collection; public readonly full: boolean; public readonly joinable: boolean; - public rtcRegion: string | null; public bitrate: number; + public nsfw: boolean; + public rtcRegion: string | null; + public rateLimitPerUser: number | null; public userLimit: number; + public videoQualityMode: VideoQualityMode | null; public createInvite(options?: CreateInviteOptions): Promise; public setRTCRegion(rtcRegion: string | null, reason?: string): Promise; public fetchInvites(cache?: boolean): Promise>; + public setBitrate(bitrate: number, reason?: string): Promise; + public setUserLimit(userLimit: number, reason?: string): Promise; + public setVideoQualityMode(videoQualityMode: VideoQualityMode | number, reason?: string): Promise; } export class BaseMessageComponent { @@ -2321,9 +2327,9 @@ export class SnowflakeUtil extends null { } export class StageChannel extends BaseGuildVoiceChannel { + public readonly stageInstance: StageInstance | null; public topic: string | null; public type: 'GUILD_STAGE_VOICE'; - public readonly stageInstance: StageInstance | null; public createStageInstance(options: StageInstanceCreateOptions): Promise; public setTopic(topic: string): Promise; } @@ -2799,17 +2805,11 @@ export class Formatters extends null { public static userMention: typeof userMention; } -export class VoiceChannel extends TextBasedChannelMixin(BaseGuildVoiceChannel, ['lastPinTimestamp', 'lastPinAt']) { - public videoQualityMode: VideoQualityMode | null; +export class VoiceChannel extends BaseGuildVoiceChannel { /** @deprecated Use manageable instead */ public readonly editable: boolean; public readonly speakable: boolean; public type: 'GUILD_VOICE'; - public nsfw: boolean; - public rateLimitPerUser: number | null; - public setBitrate(bitrate: number, reason?: string): Promise; - public setUserLimit(userLimit: number, reason?: string): Promise; - public setVideoQualityMode(videoQualityMode: VideoQualityMode | number, reason?: string): Promise; } export class VoiceRegion { @@ -3365,7 +3365,7 @@ export class GuildChannelManager extends CachedManager; public addFollower( channel: NewsChannel | Snowflake, @@ -4575,7 +4575,7 @@ export interface ClientEvents extends BaseClientEvents { typingStart: [typing: Typing]; userUpdate: [oldUser: User | PartialUser, newUser: User]; voiceStateUpdate: [oldState: VoiceState, newState: VoiceState]; - webhookUpdate: [channel: TextChannel | NewsChannel | VoiceChannel | ForumChannel]; + webhookUpdate: [channel: TextChannel | NewsChannel | VoiceChannel | ForumChannel | StageChannel]; /** @deprecated Use interactionCreate instead */ interaction: [interaction: Interaction]; interactionCreate: [interaction: Interaction]; @@ -6437,7 +6437,7 @@ export type WebhookClientOptions = Pick< export interface WebhookEditData { name?: string; avatar?: BufferResolvable | null; - channel?: GuildTextChannelResolvable; + channel?: GuildTextChannelResolvable | VoiceChannel | StageChannel | ForumChannel; } export type WebhookEditMessageOptions = Pick< diff --git a/typings/index.test-d.ts b/typings/index.test-d.ts index 2132ada450d2..735194535ed5 100644 --- a/typings/index.test-d.ts +++ b/typings/index.test-d.ts @@ -1323,7 +1323,7 @@ declare const GuildBasedChannel: GuildBasedChannel; declare const NonThreadGuildBasedChannel: NonThreadGuildBasedChannel; declare const GuildTextBasedChannel: GuildTextBasedChannel; -expectType(TextBasedChannel); +expectType(TextBasedChannel); expectType< | 'DM' | 'GUILD_NEWS' @@ -1332,6 +1332,7 @@ expectType< | 'GUILD_PRIVATE_THREAD' | 'GUILD_NEWS_THREAD' | 'GUILD_VOICE' + | 'GUILD_STAGE_VOICE' >(TextBasedChannelTypes); expectType(VoiceBasedChannel); expectType( @@ -1340,4 +1341,4 @@ expectType( NonThreadGuildBasedChannel, ); -expectType(GuildTextBasedChannel); +expectType(GuildTextBasedChannel);