diff --git a/src/client/actions/WebhooksUpdate.js b/src/client/actions/WebhooksUpdate.js index b23fe23..99259ba 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 7caa79d..5f1b804 100644 --- a/src/structures/BaseGuildVoiceChannel.js +++ b/src/structures/BaseGuildVoiceChannel.js @@ -2,24 +2,35 @@ 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); + } + _patch(data) { super._patch(data); - if ('rtc_region' in data) { - /** - * The RTC region for this voice-based channel. This region is automatically selected if `null`. - * @type {?string} - */ - this.rtcRegion = data.rtc_region; - } - if ('bitrate' in data) { /** * The bitrate of this voice-based channel @@ -28,6 +39,14 @@ class BaseGuildVoiceChannel extends GuildChannel { this.bitrate = data.bitrate; } + if ('rtc_region' in data) { + /** + * The RTC region for this voice-based channel. This region is automatically selected if `null`. + * @type {?string} + */ + this.rtcRegion = data.rtc_region; + } + if ('user_limit' in data) { /** * The maximum amount of users allowed in this channel. @@ -35,6 +54,40 @@ class BaseGuildVoiceChannel extends GuildChannel { */ this.userLimit = data.user_limit; } + + 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 = Boolean(data.nsfw); + } } /** @@ -80,22 +133,6 @@ class BaseGuildVoiceChannel extends GuildChannel { ); } - /** - * 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 - * @param {string} [reason] The reason for modifying this region. - * @returns {Promise} - * @example - * // Set the RTC region to sydney - * channel.setRTCRegion('sydney'); - * @example - * // Remove a fixed region for this channel - let Discord decide automatically - * channel.setRTCRegion(null, 'We want to let Discord decide.'); - */ - setRTCRegion(rtcRegion, reason) { - return this.edit({ rtcRegion }, reason); - } - /** * Creates an invite to this guild channel. * @param {CreateInviteOptions} [options={}] The options for creating the invite @@ -119,6 +156,79 @@ class BaseGuildVoiceChannel extends GuildChannel { 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 + * voiceChannel.setBitrate(48_000) + * .then(vc => console.log(`Set bitrate to ${vc.bitrate}bps for ${vc.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 + * @param {string} [reason] The reason for modifying this region. + * @returns {Promise} + * @example + * // Set the RTC region to sydney + * channel.setRTCRegion('sydney'); + * @example + * // Remove a fixed region for this channel - let Discord decide automatically + * channel.setRTCRegion(null, 'We want to let Discord decide.'); + */ + setRTCRegion(rtcRegion, reason) { + return this.edit({ rtcRegion }, reason); + } + + /** + * 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 + * // Set the user limit of a voice channel + * voiceChannel.setUserLimit(42) + * .then(vc => console.log(`Set user limit to ${vc.userLimit} for ${vc.name}`)) + * .catch(console.error); + */ + setUserLimit(userLimit, reason) { + return this.edit({ userLimit }, reason); + } + + /** + * 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} + */ + 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 b57beb5..f7a2d8f 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 9e3932f..e2bc579 100644 --- a/src/structures/VoiceChannel.js +++ b/src/structures/VoiceChannel.js @@ -2,10 +2,6 @@ const process = require('node:process'); const BaseGuildVoiceChannel = require('./BaseGuildVoiceChannel'); -const TextBasedChannel = require('./interfaces/TextBasedChannel'); -const InteractionManager = require('../managers/InteractionManager'); -const MessageManager = require('../managers/MessageManager'); -const { VideoQualityModes } = require('../util/Constants'); const Permissions = require('../util/Permissions'); let deprecationEmittedForEditable = false; @@ -13,71 +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); - - /** - * A manager of the interactions sent to this channel - * @type {InteractionManager} - */ - this.interactions = new InteractionManager(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} @@ -127,58 +60,16 @@ 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 user limit of the channel. - * @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}`)) - * .catch(console.error); - */ - setUserLimit(userLimit, reason) { - return this.edit({ userLimit }, reason); - } - - /** - * 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} - */ - 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. @@ -193,8 +84,27 @@ class VoiceChannel extends BaseGuildVoiceChannel { * // 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(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 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} + */ } -TextBasedChannel.applyToClass(VoiceChannel, true, ['lastPinAt']); - module.exports = VoiceChannel; diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 95e3cdb..49660a8 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -1,7 +1,6 @@ 'use strict'; const process = require('node:process'); -const { Message } = require('./Message'); const MessagePayload = require('./MessagePayload'); const { Error } = require('../errors'); const { WebhookTypes } = require('../util/Constants'); @@ -73,7 +72,7 @@ class Webhook { if ('channel_id' in data) { /** - * The id of the channel the webhook belongs to + * The channel the webhook belongs to * @type {Snowflake} */ this.channelId = data.channel_id; @@ -110,15 +109,6 @@ class Webhook { } } - /** - * The channel the webhook belongs to - * @type {?(TextChannel|VoiceChannel|NewsChannel|ForumChannel)} - * @readonly - */ - get channel() { - return this.client.channels.resolve(this.channelId); - } - /** * Options that can be passed into send. * @typedef {BaseMessageOptions} WebhookMessageOptions @@ -126,7 +116,6 @@ class Webhook { * @property {string} [avatarURL] Avatar URL override for the message * @property {Snowflake} [threadId] The id of the thread in the channel to send to. * For interaction webhooks, this property is ignored - * @property {string} [threadName] Name of the thread to create (only available if webhook is in a forum channel) * @property {MessageFlags} [flags] Which flags to set for the message. Only `SUPPRESS_EMBEDS` can be set. */ @@ -142,6 +131,7 @@ class Webhook { * Action rows containing interactive components for the message (buttons, select menus) * @property {Snowflake} [threadId] The id of the thread this message belongs to * For interaction webhooks, this property is ignored + * @property {string} [threadName] Name of the thread to create (only available if webhook is in a forum channel) */ /** @@ -198,9 +188,9 @@ class Webhook { let messagePayload; if (options instanceof MessagePayload) { - messagePayload = await options.resolveData(); + messagePayload = options.resolveData(); } else { - messagePayload = await MessagePayload.create(this, options).resolveData(); + messagePayload = MessagePayload.create(this, options).resolveData(); } const { data, files } = await messagePayload.resolveFiles(); @@ -209,8 +199,6 @@ class Webhook { files, query: { thread_id: messagePayload.options.threadId, wait: true }, auth: false, - versioned: true, - webhook: true, }); return this.client.channels?.cache.get(d.channel_id)?.messages._add(d, false) ?? d; } @@ -240,7 +228,6 @@ class Webhook { query: { wait: true }, auth: false, data: body, - webhook: true, }); return data.toString() === 'ok'; } @@ -250,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 */ /** @@ -268,7 +256,6 @@ class Webhook { data: { name, avatar, channel_id: channel }, reason, auth: !this.token || Boolean(channel), - webhook: true, }); this.name = data.name; @@ -317,7 +304,6 @@ class Webhook { thread_id: cacheOrOptions.threadId, }, auth: false, - webhook: true, }); return this.client.channels?.cache.get(data.channel_id)?.messages._add(data, cacheOrOptions.cache) ?? data; } @@ -333,12 +319,11 @@ class Webhook { if (!this.token) throw new Error('WEBHOOK_TOKEN_UNAVAILABLE'); let messagePayload; - if (options instanceof MessagePayload) { - messagePayload = await options.resolveData(); - } else { - messagePayload = await MessagePayload.create(message instanceof Message ? message : this, options).resolveData(); - } - const { data, files } = await messagePayload.resolveFiles(); + + if (options instanceof MessagePayload) messagePayload = options; + else messagePayload = MessagePayload.create(this, options); + + const { data, files } = await messagePayload.resolveData().resolveFiles(); const d = await this.client.api .webhooks(this.id, this.token) @@ -350,7 +335,6 @@ class Webhook { thread_id: messagePayload.options.threadId, }, auth: false, - webhook: true, }); const messageManager = this.client.channels?.cache.get(d.channel_id)?.messages; @@ -370,7 +354,7 @@ class Webhook { * @returns {Promise} */ async delete(reason) { - await this.client.api.webhooks(this.id, this.token).delete({ reason, auth: !this.token, webhook: true }); + await this.client.api.webhooks(this.id, this.token).delete({ reason, auth: !this.token }); } /** @@ -390,7 +374,6 @@ class Webhook { thread_id: threadId, }, auth: false, - webhook: true, }); } diff --git a/src/util/Constants.js b/src/util/Constants.js index 183965c..e5edc57 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -897,7 +897,8 @@ exports.ChannelTypes = createEnum([ * * NewsChannel * * ThreadChannel * * VoiceChannel - * @typedef {DMChannel|TextChannel|NewsChannel|ThreadChannel|VoiceChannel} TextBasedChannels + * * StageChannel + * @typedef {DMChannel|TextChannel|NewsChannel|ThreadChannel|VoiceChannel|StageChannel} TextBasedChannels */ /** @@ -916,6 +917,7 @@ exports.ChannelTypes = createEnum([ * * GUILD_PUBLIC_THREAD * * GUILD_PRIVATE_THREAD * * GUILD_VOICE + * * GUILD_STAGE_VOICE * @typedef {string} TextBasedChannelTypes */ exports.TextBasedChannelTypes = [ @@ -926,6 +928,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 4e6d8c0..3957666 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -784,17 +784,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 { @@ -2910,9 +2916,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; } @@ -3358,17 +3364,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 { @@ -4027,7 +4027,7 @@ export class GuildChannelManager extends CachedManager; public addFollower( channel: NewsChannel | Snowflake, @@ -4789,7 +4789,7 @@ export interface ClientEvents extends BaseClientEvents { userSettingsUpdate: [setting: RawUserSettingsData]; userGuildSettingsUpdate: [guild: Guild]; 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 | { nonce: Snowflake; id: Snowflake }]; @@ -7419,7 +7419,7 @@ export type WebhookClientOptions = Pick< export interface WebhookEditData { name?: string; avatar?: BufferResolvable | null; - channel?: GuildTextChannelResolvable; + channel?: GuildTextChannelResolvable | VoiceChannel | StageChannel | ForumChannel; } export type WebhookEditMessageOptions = Pick<