diff --git a/packages/builders/src/interactions/slashCommands/mixins/ApplicationCommandOptionChannelTypesMixin.ts b/packages/builders/src/interactions/slashCommands/mixins/ApplicationCommandOptionChannelTypesMixin.ts index c97d996e7fb4..4d112fb71d74 100644 --- a/packages/builders/src/interactions/slashCommands/mixins/ApplicationCommandOptionChannelTypesMixin.ts +++ b/packages/builders/src/interactions/slashCommands/mixins/ApplicationCommandOptionChannelTypesMixin.ts @@ -17,6 +17,7 @@ const allowedChannelTypes = [ ChannelType.PrivateThread, ChannelType.GuildStageVoice, ChannelType.GuildForum, + ChannelType.GuildMedia, ] as const; /** diff --git a/packages/discord.js/src/client/actions/WebhooksUpdate.js b/packages/discord.js/src/client/actions/WebhooksUpdate.js index 2bf41ba2702f..2f0394c19186 100644 --- a/packages/discord.js/src/client/actions/WebhooksUpdate.js +++ b/packages/discord.js/src/client/actions/WebhooksUpdate.js @@ -15,7 +15,7 @@ class WebhooksUpdate extends Action { /** * Emitted whenever a channel has its webhooks changed. * @event Client#webhooksUpdate - * @param {TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel} channel + * @param {TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel|MediaChannel} channel * The channel that had a webhook update */ client.emit('webhooksUpdate', channel); @@ -23,7 +23,7 @@ class WebhooksUpdate extends Action { /** * Emitted whenever a channel has its webhooks changed. * @event Client#webhookUpdate - * @param {TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel} channel + * @param {TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel|MediaChannel} channel * The channel that had a webhook update * @deprecated Use {@link Client#event:webhooksUpdate} instead. */ diff --git a/packages/discord.js/src/index.js b/packages/discord.js/src/index.js index 502133141320..024dec06ba30 100644 --- a/packages/discord.js/src/index.js +++ b/packages/discord.js/src/index.js @@ -149,6 +149,7 @@ exports.Message = require('./structures/Message').Message; exports.Attachment = require('./structures/Attachment'); exports.AttachmentBuilder = require('./structures/AttachmentBuilder'); exports.ModalBuilder = require('./structures/ModalBuilder'); +exports.MediaChannel = require('./structures/MediaChannel'); exports.MessageCollector = require('./structures/MessageCollector'); exports.MessageComponentInteraction = require('./structures/MessageComponentInteraction'); exports.MessageContextMenuCommandInteraction = require('./structures/MessageContextMenuCommandInteraction'); @@ -199,6 +200,7 @@ exports.TextInputBuilder = require('./structures/TextInputBuilder'); exports.TextInputComponent = require('./structures/TextInputComponent'); exports.ThreadChannel = require('./structures/ThreadChannel'); exports.ThreadMember = require('./structures/ThreadMember'); +exports.ThreadOnlyChannel = require('./structures/ThreadOnlyChannel'); exports.Typing = require('./structures/Typing'); exports.User = require('./structures/User'); exports.UserContextMenuCommandInteraction = require('./structures/UserContextMenuCommandInteraction'); diff --git a/packages/discord.js/src/managers/GuildChannelManager.js b/packages/discord.js/src/managers/GuildChannelManager.js index 7ca9287d8af8..2c5d1466cbd7 100644 --- a/packages/discord.js/src/managers/GuildChannelManager.js +++ b/packages/discord.js/src/managers/GuildChannelManager.js @@ -194,7 +194,7 @@ class GuildChannelManager extends CachedManager { /** * @typedef {ChannelWebhookCreateOptions} WebhookCreateOptions - * @property {TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel|Snowflake} channel + * @property {TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel|MediaChannel|Snowflake} channel * The channel to create the webhook for */ diff --git a/packages/discord.js/src/managers/GuildInviteManager.js b/packages/discord.js/src/managers/GuildInviteManager.js index f1fe3eb2fa94..b7fe71bed1c0 100644 --- a/packages/discord.js/src/managers/GuildInviteManager.js +++ b/packages/discord.js/src/managers/GuildInviteManager.js @@ -46,8 +46,9 @@ class GuildInviteManager extends CachedManager { * * NewsChannel * * StageChannel * * ForumChannel + * * MediaChannel * * Snowflake - * @typedef {TextChannel|VoiceChannel|NewsChannel|StageChannel|ForumChannel|Snowflake} + * @typedef {TextChannel|VoiceChannel|NewsChannel|StageChannel|ForumChannel|MediaChannel|Snowflake} * GuildInvitableChannelResolvable */ diff --git a/packages/discord.js/src/managers/ThreadManager.js b/packages/discord.js/src/managers/ThreadManager.js index 17569f5b97fe..c4b2e60f6dca 100644 --- a/packages/discord.js/src/managers/ThreadManager.js +++ b/packages/discord.js/src/managers/ThreadManager.js @@ -20,7 +20,7 @@ class ThreadManager extends CachedManager { /** * The channel this Manager belongs to - * @type {TextChannel|NewsChannel|ForumChannel} + * @type {TextChannel|NewsChannel|ForumChannel|MediaChannel} */ this.channel = channel; } diff --git a/packages/discord.js/src/structures/AutoModerationActionExecution.js b/packages/discord.js/src/structures/AutoModerationActionExecution.js index fcbc6170bdd0..f71c361d3619 100644 --- a/packages/discord.js/src/structures/AutoModerationActionExecution.js +++ b/packages/discord.js/src/structures/AutoModerationActionExecution.js @@ -87,7 +87,7 @@ class AutoModerationActionExecution { /** * The channel where this action was triggered from. - * @type {?(GuildTextBasedChannel|ForumChannel)} + * @type {?(GuildTextBasedChannel|ForumChannel|MediaChannel)} * @readonly */ get channel() { diff --git a/packages/discord.js/src/structures/ForumChannel.js b/packages/discord.js/src/structures/ForumChannel.js index 87e647820424..9c1f9a682aba 100644 --- a/packages/discord.js/src/structures/ForumChannel.js +++ b/packages/discord.js/src/structures/ForumChannel.js @@ -1,139 +1,14 @@ 'use strict'; -const GuildChannel = require('./GuildChannel'); -const TextBasedChannel = require('./interfaces/TextBasedChannel'); -const GuildForumThreadManager = require('../managers/GuildForumThreadManager'); -const { transformAPIGuildForumTag, transformAPIGuildDefaultReaction } = require('../util/Channels'); +const ThreadOnlyChannel = require('./ThreadOnlyChannel'); /** - * @typedef {Object} GuildForumTagEmoji - * @property {?Snowflake} id The id of a guild's custom emoji - * @property {?string} name The unicode character of the emoji + * Represents a forum channel. + * @extends {ThreadOnlyChannel} */ - -/** - * @typedef {Object} GuildForumTag - * @property {Snowflake} id The id of the tag - * @property {string} name The name of the tag - * @property {boolean} moderated Whether this tag can only be added to or removed from threads - * by a member with the `ManageThreads` permission - * @property {?GuildForumTagEmoji} emoji The emoji of this tag - */ - -/** - * @typedef {Object} GuildForumTagData - * @property {Snowflake} [id] The id of the tag - * @property {string} name The name of the tag - * @property {boolean} [moderated] Whether this tag can only be added to or removed from threads - * by a member with the `ManageThreads` permission - * @property {?GuildForumTagEmoji} [emoji] The emoji of this tag - */ - -/** - * @typedef {Object} DefaultReactionEmoji - * @property {?Snowflake} id The id of a guild's custom emoji - * @property {?string} name The unicode character of the emoji - */ - -/** - * Represents a channel that only contains threads - * @extends {GuildChannel} - * @implements {TextBasedChannel} - */ -class ForumChannel extends GuildChannel { - constructor(guild, data, client) { - super(guild, data, client, false); - - /** - * A manager of the threads belonging to this channel - * @type {GuildForumThreadManager} - */ - this.threads = new GuildForumThreadManager(this); - - this._patch(data); - } - +class ForumChannel extends ThreadOnlyChannel { _patch(data) { super._patch(data); - if ('available_tags' in data) { - /** - * The set of tags that can be used in this channel. - * @type {GuildForumTag[]} - */ - this.availableTags = data.available_tags.map(tag => transformAPIGuildForumTag(tag)); - } else { - this.availableTags ??= []; - } - - if ('default_reaction_emoji' in data) { - /** - * The emoji to show in the add reaction button on a thread in a guild forum channel - * @type {?DefaultReactionEmoji} - */ - this.defaultReactionEmoji = data.default_reaction_emoji - ? transformAPIGuildDefaultReaction(data.default_reaction_emoji) - : null; - } else { - this.defaultReactionEmoji ??= null; - } - - if ('default_thread_rate_limit_per_user' in data) { - /** - * The initial rate limit per user (slowmode) to set on newly created threads in a channel. - * @type {?number} - */ - this.defaultThreadRateLimitPerUser = data.default_thread_rate_limit_per_user; - } else { - this.defaultThreadRateLimitPerUser ??= null; - } - - if ('rate_limit_per_user' in data) { - /** - * The rate limit per user (slowmode) for this channel. - * @type {?number} - */ - this.rateLimitPerUser = data.rate_limit_per_user; - } else { - this.rateLimitPerUser ??= null; - } - - if ('default_auto_archive_duration' in data) { - /** - * The default auto archive duration for newly created threads in this channel. - * @type {?ThreadAutoArchiveDuration} - */ - this.defaultAutoArchiveDuration = data.default_auto_archive_duration; - } else { - this.defaultAutoArchiveDuration ??= null; - } - - if ('nsfw' in data) { - /** - * If this channel is considered NSFW. - * @type {boolean} - */ - this.nsfw = data.nsfw; - } else { - this.nsfw ??= false; - } - - if ('topic' in data) { - /** - * The topic of this channel. - * @type {?string} - */ - this.topic = data.topic; - } - - if ('default_sort_order' in data) { - /** - * The default sort order mode used to order posts - * @type {?SortOrderType} - */ - this.defaultSortOrder = data.default_sort_order; - } else { - this.defaultSortOrder ??= null; - } /** * The default layout type used to display posts @@ -142,95 +17,6 @@ class ForumChannel extends GuildChannel { this.defaultForumLayout = data.default_forum_layout; } - /** - * Sets the available tags for this forum channel - * @param {GuildForumTagData[]} availableTags The tags to set as available in this channel - * @param {string} [reason] Reason for changing the available tags - * @returns {Promise} - */ - setAvailableTags(availableTags, reason) { - return this.edit({ availableTags, reason }); - } - - /** - * Sets the default reaction emoji for this channel - * @param {?DefaultReactionEmoji} defaultReactionEmoji The emoji to set as the default reaction emoji - * @param {string} [reason] Reason for changing the default reaction emoji - * @returns {Promise} - */ - setDefaultReactionEmoji(defaultReactionEmoji, reason) { - return this.edit({ defaultReactionEmoji, reason }); - } - - /** - * Sets the default rate limit per user (slowmode) for new threads in this channel - * @param {number} defaultThreadRateLimitPerUser The rate limit to set on newly created threads in this channel - * @param {string} [reason] Reason for changing the default rate limit - * @returns {Promise} - */ - setDefaultThreadRateLimitPerUser(defaultThreadRateLimitPerUser, reason) { - return this.edit({ defaultThreadRateLimitPerUser, reason }); - } - - /** - * Creates an invite to this guild channel. - * @param {InviteCreateOptions} [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 to cache the fetched invites - * @returns {Promise>} - */ - fetchInvites(cache) { - return this.guild.invites.fetch({ channelId: this.id, cache }); - } - - /** - * Sets the default auto archive duration for all newly created threads in this channel. - * @param {ThreadAutoArchiveDuration} defaultAutoArchiveDuration The new default auto archive duration - * @param {string} [reason] Reason for changing the channel's default auto archive duration - * @returns {Promise} - */ - setDefaultAutoArchiveDuration(defaultAutoArchiveDuration, reason) { - return this.edit({ defaultAutoArchiveDuration, reason }); - } - - /** - * Sets a new topic for the guild channel. - * @param {?string} topic The new topic for the guild channel - * @param {string} [reason] Reason for changing the guild channel's topic - * @returns {Promise} - * @example - * // Set a new channel topic - * channel.setTopic('needs more rate limiting') - * .then(newChannel => console.log(`Channel's new topic is ${newChannel.topic}`)) - * .catch(console.error); - */ - setTopic(topic, reason) { - return this.edit({ topic, reason }); - } - - /** - * Sets the default sort order mode used to order posts - * @param {?SortOrderType} defaultSortOrder The default sort order mode to set on this channel - * @param {string} [reason] Reason for changing the default sort order - * @returns {Promise} - */ - setDefaultSortOrder(defaultSortOrder, reason) { - return this.edit({ defaultSortOrder, reason }); - } - /** * Sets the default forum layout type used to display posts * @param {ForumLayoutType} defaultForumLayout The default forum layout type to set on this channel @@ -240,25 +26,6 @@ class ForumChannel extends GuildChannel { setDefaultForumLayout(defaultForumLayout, reason) { return this.edit({ defaultForumLayout, reason }); } - - // These are here only for documentation purposes - they are implemented by TextBasedChannel - /* eslint-disable no-empty-function */ - createWebhook() {} - fetchWebhooks() {} - setNSFW() {} - setRateLimitPerUser() {} } -TextBasedChannel.applyToClass(ForumChannel, true, [ - 'send', - 'lastMessage', - 'lastPinAt', - 'bulkDelete', - 'sendTyping', - 'createMessageCollector', - 'awaitMessages', - 'createMessageComponentCollector', - 'awaitMessageComponent', -]); - module.exports = ForumChannel; diff --git a/packages/discord.js/src/structures/Guild.js b/packages/discord.js/src/structures/Guild.js index 5123ee56586c..329026e126c2 100644 --- a/packages/discord.js/src/structures/Guild.js +++ b/packages/discord.js/src/structures/Guild.js @@ -520,7 +520,7 @@ class Guild extends AnonymousGuild { /** * Widget channel for this guild - * @type {?(TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel)} + * @type {?(TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel|MediaChannel)} * @readonly */ get widgetChannel() { @@ -693,14 +693,15 @@ class Guild extends AnonymousGuild { * Data for the Guild Widget Settings object * @typedef {Object} GuildWidgetSettings * @property {boolean} enabled Whether the widget is enabled - * @property {?(TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel)} channel The widget invite channel + * @property {?(TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel|MediaChannel)} channel + * The widget invite channel */ /** * The Guild Widget Settings object * @typedef {Object} GuildWidgetSettingsData * @property {boolean} enabled Whether the widget is enabled - * @property {?(TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel|Snowflake)} channel + * @property {?(TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel|MediaChannel|Snowflake)} channel * The widget invite channel */ @@ -884,7 +885,8 @@ class Guild extends AnonymousGuild { * Welcome channel data * @typedef {Object} WelcomeChannelData * @property {string} description The description to show for this welcome channel - * @property {TextChannel|NewsChannel|ForumChannel|Snowflake} channel The channel to link for this welcome channel + * @property {TextChannel|NewsChannel|ForumChannel|MediaChannel|Snowflake} channel + * The channel to link for this welcome channel * @property {EmojiIdentifierResolvable} [emoji] The emoji to display for this welcome channel */ diff --git a/packages/discord.js/src/structures/GuildChannel.js b/packages/discord.js/src/structures/GuildChannel.js index c066c71d6d37..dee674d5b7ce 100644 --- a/packages/discord.js/src/structures/GuildChannel.js +++ b/packages/discord.js/src/structures/GuildChannel.js @@ -17,6 +17,7 @@ const { getSortableGroupTypes } = require('../util/Util'); * - {@link NewsChannel} * - {@link StageChannel} * - {@link ForumChannel} + * - {@link MediaChannel} * @extends {BaseChannel} * @abstract */ diff --git a/packages/discord.js/src/structures/MediaChannel.js b/packages/discord.js/src/structures/MediaChannel.js new file mode 100644 index 000000000000..5b21c410976b --- /dev/null +++ b/packages/discord.js/src/structures/MediaChannel.js @@ -0,0 +1,11 @@ +'use strict'; + +const ThreadOnlyChannel = require('./ThreadOnlyChannel'); + +/** + * Represents a media channel. + * @extends {ThreadOnlyChannel} + */ +class MediaChannel extends ThreadOnlyChannel {} + +module.exports = MediaChannel; diff --git a/packages/discord.js/src/structures/ThreadChannel.js b/packages/discord.js/src/structures/ThreadChannel.js index 96b408701ade..913767f54182 100644 --- a/packages/discord.js/src/structures/ThreadChannel.js +++ b/packages/discord.js/src/structures/ThreadChannel.js @@ -1,7 +1,9 @@ 'use strict'; +const { lazy } = require('@discordjs/util'); const { ChannelType, PermissionFlagsBits, Routes, ChannelFlags } = require('discord-api-types/v10'); const { BaseChannel } = require('./BaseChannel'); +const getThreadOnlyChannel = lazy(() => require('./ThreadOnlyChannel')); const TextBasedChannel = require('./interfaces/TextBasedChannel'); const { DiscordjsRangeError, ErrorCodes } = require('../errors'); const GuildMessageManager = require('../managers/GuildMessageManager'); @@ -248,7 +250,7 @@ class ThreadChannel extends BaseChannel { /** * The parent channel of this thread - * @type {?(NewsChannel|TextChannel|ForumChannel)} + * @type {?(NewsChannel|TextChannel|ForumChannel|MediaChannel)} * @readonly */ get parent() { @@ -312,7 +314,7 @@ class ThreadChannel extends BaseChannel { */ // eslint-disable-next-line require-await async fetchStarterMessage(options) { - const channel = this.parent?.type === ChannelType.GuildForum ? this : this.parent; + const channel = this.parent instanceof getThreadOnlyChannel() ? this : this.parent; return channel?.messages.fetch({ message: this.id, ...options }) ?? null; } diff --git a/packages/discord.js/src/structures/ThreadOnlyChannel.js b/packages/discord.js/src/structures/ThreadOnlyChannel.js new file mode 100644 index 000000000000..1f402ca5989a --- /dev/null +++ b/packages/discord.js/src/structures/ThreadOnlyChannel.js @@ -0,0 +1,249 @@ +'use strict'; + +const GuildChannel = require('./GuildChannel'); +const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const GuildForumThreadManager = require('../managers/GuildForumThreadManager'); +const { transformAPIGuildForumTag, transformAPIGuildDefaultReaction } = require('../util/Channels'); + +/** + * @typedef {Object} GuildForumTagEmoji + * @property {?Snowflake} id The id of a guild's custom emoji + * @property {?string} name The unicode character of the emoji + */ + +/** + * @typedef {Object} GuildForumTag + * @property {Snowflake} id The id of the tag + * @property {string} name The name of the tag + * @property {boolean} moderated Whether this tag can only be added to or removed from threads + * by a member with the `ManageThreads` permission + * @property {?GuildForumTagEmoji} emoji The emoji of this tag + */ + +/** + * @typedef {Object} GuildForumTagData + * @property {Snowflake} [id] The id of the tag + * @property {string} name The name of the tag + * @property {boolean} [moderated] Whether this tag can only be added to or removed from threads + * by a member with the `ManageThreads` permission + * @property {?GuildForumTagEmoji} [emoji] The emoji of this tag + */ + +/** + * @typedef {Object} DefaultReactionEmoji + * @property {?Snowflake} id The id of a guild's custom emoji + * @property {?string} name The unicode character of the emoji + */ + +/** + * Represents symbols utilised by thread-only channels. + * @extends {GuildChannel} + * @implements {TextBasedChannel} + * @abstract + */ +class ThreadOnlyChannel extends GuildChannel { + constructor(guild, data, client) { + super(guild, data, client, false); + + /** + * A manager of the threads belonging to this channel + * @type {GuildForumThreadManager} + */ + this.threads = new GuildForumThreadManager(this); + + this._patch(data); + } + + _patch(data) { + super._patch(data); + if ('available_tags' in data) { + /** + * The set of tags that can be used in this channel. + * @type {GuildForumTag[]} + */ + this.availableTags = data.available_tags.map(tag => transformAPIGuildForumTag(tag)); + } else { + this.availableTags ??= []; + } + + if ('default_reaction_emoji' in data) { + /** + * The emoji to show in the add reaction button on a thread in a guild forum channel + * @type {?DefaultReactionEmoji} + */ + this.defaultReactionEmoji = data.default_reaction_emoji + ? transformAPIGuildDefaultReaction(data.default_reaction_emoji) + : null; + } else { + this.defaultReactionEmoji ??= null; + } + + if ('default_thread_rate_limit_per_user' in data) { + /** + * The initial rate limit per user (slowmode) to set on newly created threads in a channel. + * @type {?number} + */ + this.defaultThreadRateLimitPerUser = data.default_thread_rate_limit_per_user; + } else { + this.defaultThreadRateLimitPerUser ??= null; + } + + if ('rate_limit_per_user' in data) { + /** + * The rate limit per user (slowmode) for this channel. + * @type {?number} + */ + this.rateLimitPerUser = data.rate_limit_per_user; + } else { + this.rateLimitPerUser ??= null; + } + + if ('default_auto_archive_duration' in data) { + /** + * The default auto archive duration for newly created threads in this channel. + * @type {?ThreadAutoArchiveDuration} + */ + this.defaultAutoArchiveDuration = data.default_auto_archive_duration; + } else { + this.defaultAutoArchiveDuration ??= null; + } + + if ('nsfw' in data) { + /** + * If this channel is considered NSFW. + * @type {boolean} + */ + this.nsfw = data.nsfw; + } else { + this.nsfw ??= false; + } + + if ('topic' in data) { + /** + * The topic of this channel. + * @type {?string} + */ + this.topic = data.topic; + } + + if ('default_sort_order' in data) { + /** + * The default sort order mode used to order posts + * @type {?SortOrderType} + */ + this.defaultSortOrder = data.default_sort_order; + } else { + this.defaultSortOrder ??= null; + } + } + + /** + * Sets the available tags for this forum channel + * @param {GuildForumTagData[]} availableTags The tags to set as available in this channel + * @param {string} [reason] Reason for changing the available tags + * @returns {Promise} + */ + setAvailableTags(availableTags, reason) { + return this.edit({ availableTags, reason }); + } + + /** + * Sets the default reaction emoji for this channel + * @param {?DefaultReactionEmoji} defaultReactionEmoji The emoji to set as the default reaction emoji + * @param {string} [reason] Reason for changing the default reaction emoji + * @returns {Promise} + */ + setDefaultReactionEmoji(defaultReactionEmoji, reason) { + return this.edit({ defaultReactionEmoji, reason }); + } + + /** + * Sets the default rate limit per user (slowmode) for new threads in this channel + * @param {number} defaultThreadRateLimitPerUser The rate limit to set on newly created threads in this channel + * @param {string} [reason] Reason for changing the default rate limit + * @returns {Promise} + */ + setDefaultThreadRateLimitPerUser(defaultThreadRateLimitPerUser, reason) { + return this.edit({ defaultThreadRateLimitPerUser, reason }); + } + + /** + * Creates an invite to this guild channel. + * @param {InviteCreateOptions} [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 to cache the fetched invites + * @returns {Promise>} + */ + fetchInvites(cache) { + return this.guild.invites.fetch({ channelId: this.id, cache }); + } + + /** + * Sets the default auto archive duration for all newly created threads in this channel. + * @param {ThreadAutoArchiveDuration} defaultAutoArchiveDuration The new default auto archive duration + * @param {string} [reason] Reason for changing the channel's default auto archive duration + * @returns {Promise} + */ + setDefaultAutoArchiveDuration(defaultAutoArchiveDuration, reason) { + return this.edit({ defaultAutoArchiveDuration, reason }); + } + + /** + * Sets a new topic for the guild channel. + * @param {?string} topic The new topic for the guild channel + * @param {string} [reason] Reason for changing the guild channel's topic + * @returns {Promise} + * @example + * // Set a new channel topic + * channel.setTopic('needs more rate limiting') + * .then(newChannel => console.log(`Channel's new topic is ${newChannel.topic}`)) + * .catch(console.error); + */ + setTopic(topic, reason) { + return this.edit({ topic, reason }); + } + + /** + * Sets the default sort order mode used to order posts + * @param {?SortOrderType} defaultSortOrder The default sort order mode to set on this channel + * @param {string} [reason] Reason for changing the default sort order + * @returns {Promise} + */ + setDefaultSortOrder(defaultSortOrder, reason) { + return this.edit({ defaultSortOrder, reason }); + } + + // These are here only for documentation purposes - they are implemented by TextBasedChannel + /* eslint-disable no-empty-function */ + createWebhook() {} + fetchWebhooks() {} + setNSFW() {} + setRateLimitPerUser() {} +} + +TextBasedChannel.applyToClass(ThreadOnlyChannel, true, [ + 'send', + 'lastMessage', + 'lastPinAt', + 'bulkDelete', + 'sendTyping', + 'createMessageCollector', + 'awaitMessages', + 'createMessageComponentCollector', + 'awaitMessageComponent', +]); + +module.exports = ThreadOnlyChannel; diff --git a/packages/discord.js/src/structures/Webhook.js b/packages/discord.js/src/structures/Webhook.js index 738d9e765b03..1257c316241c 100644 --- a/packages/discord.js/src/structures/Webhook.js +++ b/packages/discord.js/src/structures/Webhook.js @@ -147,7 +147,7 @@ class Webhook { /** * The channel the webhook belongs to - * @type {?(TextChannel|VoiceChannel|StageChannel|NewsChannel|ForumChannel)} + * @type {?(TextChannel|VoiceChannel|StageChannel|NewsChannel|ForumChannel|MediaChannel)} * @readonly */ get channel() { @@ -264,7 +264,7 @@ class Webhook { * @typedef {Object} WebhookEditOptions * @property {string} [name=this.name] The new name for the webhook * @property {?(BufferResolvable)} [avatar] The new avatar for the webhook - * @property {GuildTextChannelResolvable|VoiceChannel|StageChannel|ForumChannel} [channel] + * @property {GuildTextChannelResolvable|VoiceChannel|StageChannel|ForumChannel|MediaChannel} [channel] * The new channel for the webhook * @property {string} [reason] Reason for editing the webhook */ diff --git a/packages/discord.js/src/structures/WelcomeChannel.js b/packages/discord.js/src/structures/WelcomeChannel.js index d783e0610e99..3ca99a156c48 100644 --- a/packages/discord.js/src/structures/WelcomeChannel.js +++ b/packages/discord.js/src/structures/WelcomeChannel.js @@ -42,7 +42,7 @@ class WelcomeChannel extends Base { /** * The channel of this welcome channel - * @type {?(TextChannel|NewsChannel|ForumChannel)} + * @type {?(TextChannel|NewsChannel|ForumChannel|MediaChannel)} */ get channel() { return this.client.channels.resolve(this.channelId); diff --git a/packages/discord.js/src/util/Channels.js b/packages/discord.js/src/util/Channels.js index 8d070bd53ddd..daf336e1743f 100644 --- a/packages/discord.js/src/util/Channels.js +++ b/packages/discord.js/src/util/Channels.js @@ -13,6 +13,7 @@ const getVoiceChannel = lazy(() => require('../structures/VoiceChannel')); const getDirectoryChannel = lazy(() => require('../structures/DirectoryChannel')); const getPartialGroupDMChannel = lazy(() => require('../structures/PartialGroupDMChannel')); const getForumChannel = lazy(() => require('../structures/ForumChannel')); +const getMediaChannel = lazy(() => require('../structures/MediaChannel')); /** * Creates a discord.js channel from data received from the API. @@ -69,6 +70,9 @@ function createChannel(client, data, guild, { allowUnknownGuild } = {}) { case ChannelType.GuildForum: channel = new (getForumChannel())(guild, data, client); break; + case ChannelType.GuildMedia: + channel = new (getMediaChannel())(guild, data, client); + break; } if (channel && !allowUnknownGuild) guild.channels?.cache.set(channel.id, channel); } diff --git a/packages/discord.js/src/util/Util.js b/packages/discord.js/src/util/Util.js index e42bc5a11a6c..5814af103002 100644 --- a/packages/discord.js/src/util/Util.js +++ b/packages/discord.js/src/util/Util.js @@ -163,7 +163,13 @@ function makePlainError(err) { }; } -const TextSortableGroupTypes = [ChannelType.GuildText, ChannelType.GuildAnnouncement, ChannelType.GuildForum]; +const TextSortableGroupTypes = [ + ChannelType.GuildText, + ChannelType.GuildAnnouncement, + ChannelType.GuildForum, + ChannelType.GuildMedia, +]; + const VoiceSortableGroupTypes = [ChannelType.GuildVoice, ChannelType.GuildStageVoice]; const CategorySortableGroupTypes = [ChannelType.GuildCategory]; @@ -181,6 +187,7 @@ function getSortableGroupTypes(type) { case ChannelType.GuildText: case ChannelType.GuildAnnouncement: case ChannelType.GuildForum: + case ChannelType.GuildMedia: return TextSortableGroupTypes; case ChannelType.GuildVoice: case ChannelType.GuildStageVoice: diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 7c827bb0564d..ff98527e221c 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -357,7 +357,7 @@ export class AutoModerationActionExecution { public ruleTriggerType: AutoModerationRuleTriggerType; public get user(): User | null; public userId: Snowflake; - public get channel(): GuildTextBasedChannel | ForumChannel | null; + public get channel(): GuildTextBasedChannel | ForumChannel | MediaChannel | null; public channelId: Snowflake | null; public get member(): GuildMember | null; public messageId: Snowflake | null; @@ -894,6 +894,7 @@ export interface MappedChannelCategoryTypes { [ChannelType.GuildText]: TextChannel; [ChannelType.GuildStageVoice]: StageChannel; [ChannelType.GuildForum]: ForumChannel; + [ChannelType.GuildMedia]: MediaChannel; } export type CategoryChannelType = Exclude< @@ -905,8 +906,6 @@ export type CategoryChannelType = Exclude< | ChannelType.PrivateThread | ChannelType.GuildCategory | ChannelType.GuildDirectory - // TODO: https://github.com/discordjs/discord.js/pull/9662 - | ChannelType.GuildMedia >; export class CategoryChannel extends GuildChannel { @@ -1364,7 +1363,7 @@ export class Guild extends AnonymousGuild { public vanityURLUses: number | null; public get voiceAdapterCreator(): InternalDiscordGatewayAdapterCreator; public voiceStates: VoiceStateManager; - public get widgetChannel(): TextChannel | NewsChannel | VoiceBasedChannel | ForumChannel | null; + public get widgetChannel(): TextChannel | NewsChannel | VoiceBasedChannel | ForumChannel | MediaChannel | null; public widgetChannelId: Snowflake | null; public widgetEnabled: boolean | null; public get maximumBitrate(): number; @@ -2394,7 +2393,7 @@ export interface DefaultReactionEmoji { name: string | null; } -export class ForumChannel extends TextBasedChannelMixin(GuildChannel, true, [ +export abstract class ThreadOnlyChannel extends TextBasedChannelMixin(GuildChannel, true, [ 'send', 'lastMessage', 'lastPinAt', @@ -2405,7 +2404,7 @@ export class ForumChannel extends TextBasedChannelMixin(GuildChannel, true, [ 'createMessageComponentCollector', 'awaitMessageComponent', ]) { - public type: ChannelType.GuildForum; + public type: ChannelType.GuildForum | ChannelType.GuildMedia; public threads: GuildForumThreadManager; public availableTags: GuildForumTag[]; public defaultReactionEmoji: DefaultReactionEmoji | null; @@ -2415,8 +2414,6 @@ export class ForumChannel extends TextBasedChannelMixin(GuildChannel, true, [ public nsfw: boolean; public topic: string | null; public defaultSortOrder: SortOrderType | null; - public defaultForumLayout: ForumLayoutType; - public setAvailableTags(tags: GuildForumTagData[], reason?: string): Promise; public setDefaultReactionEmoji(emojiId: DefaultReactionEmoji | null, reason?: string): Promise; public setDefaultThreadRateLimitPerUser(rateLimit: number, reason?: string): Promise; @@ -2428,9 +2425,18 @@ export class ForumChannel extends TextBasedChannelMixin(GuildChannel, true, [ ): Promise; public setTopic(topic: string | null, reason?: string): Promise; public setDefaultSortOrder(defaultSortOrder: SortOrderType | null, reason?: string): Promise; +} + +export class ForumChannel extends ThreadOnlyChannel { + public type: ChannelType.GuildForum; + public defaultForumLayout: ForumLayoutType; public setDefaultForumLayout(defaultForumLayout: ForumLayoutType, reason?: string): Promise; } +export class MediaChannel extends ThreadOnlyChannel { + public type: ChannelType.GuildMedia; +} + export class PermissionOverwrites extends Base { private constructor(client: Client, data: RawPermissionOverwriteData, channel: NonThreadGuildBasedChannel); public allow: Readonly; @@ -3025,7 +3031,7 @@ export interface PrivateThreadChannel extends ThreadChannel { type: ChannelType.PrivateThread; } -export class ThreadChannel extends TextBasedChannelMixin(BaseChannel, true, [ +export class ThreadChannel extends TextBasedChannelMixin(BaseChannel, true, [ 'fetchWebhooks', 'createWebhook', 'setNSFW', @@ -3057,7 +3063,7 @@ export class ThreadChannel extends TextBasedCha public members: ThreadMemberManager; public name: string; public ownerId: Snowflake | null; - public get parent(): If | null; + public get parent(): If | null; public parentId: Snowflake | null; public rateLimitPerUser: number | null; public type: ThreadChannelType; @@ -3343,7 +3349,7 @@ export class Webhook extends WebhookMixin() { public token: string | null; public type: WebhookType; public applicationId: Snowflake | null; - public get channel(): TextChannel | VoiceChannel | NewsChannel | ForumChannel | StageChannel | null; + public get channel(): TextChannel | VoiceChannel | NewsChannel | StageChannel | ForumChannel | MediaChannel | null; public isUserCreated(): this is this & { type: WebhookType.Incoming; applicationId: null; @@ -3488,7 +3494,7 @@ export class WelcomeChannel extends Base { public channelId: Snowflake; public guild: Guild | InviteGuild; public description: string; - public get channel(): TextChannel | NewsChannel | ForumChannel | null; + public get channel(): TextChannel | NewsChannel | ForumChannel | MediaChannel | null; public get emoji(): GuildEmoji | Emoji; } @@ -4200,13 +4206,16 @@ export class StageInstanceManager extends CachedManager; } -export class ThreadManager extends CachedManager< +export class ThreadManager extends CachedManager< Snowflake, - ThreadChannel, + ThreadChannel, ThreadChannelResolvable > { - protected constructor(channel: TextChannel | NewsChannel | ForumChannel, iterable?: Iterable); - public channel: If; + protected constructor( + channel: TextChannel | NewsChannel | ForumChannel | MediaChannel, + iterable?: Iterable, + ); + public channel: If; public fetch(options: ThreadChannelResolvable, cacheOptions?: BaseFetchOptions): Promise; public fetch( options: FetchThreadsOptions & { archived: FetchArchivedThreadOptions }, @@ -4847,7 +4856,7 @@ export interface ChannelWebhookCreateOptions { } export interface WebhookCreateOptions extends ChannelWebhookCreateOptions { - channel: TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | Snowflake; + channel: TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel | Snowflake; } export interface ClientEvents { @@ -4925,7 +4934,7 @@ export interface ClientEvents { voiceStateUpdate: [oldState: VoiceState, newState: VoiceState]; /** @deprecated Use {@link webhooksUpdate} instead. */ webhookUpdate: ClientEvents['webhooksUpdate']; - webhooksUpdate: [channel: TextChannel | NewsChannel | VoiceChannel | ForumChannel]; + webhooksUpdate: [channel: TextChannel | NewsChannel | VoiceChannel | ForumChannel | MediaChannel]; interactionCreate: [interaction: Interaction]; shardDisconnect: [closeEvent: CloseEvent, shardId: number]; shardError: [error: Error, shardId: number]; @@ -5610,7 +5619,7 @@ export interface GuildCreateOptions { export interface GuildWidgetSettings { enabled: boolean; - channel: TextChannel | NewsChannel | VoiceBasedChannel | ForumChannel | null; + channel: TextChannel | NewsChannel | VoiceBasedChannel | ForumChannel | MediaChannel | null; } export interface GuildEditOptions { @@ -5690,7 +5699,7 @@ export interface GuildPruneMembersOptions { export interface GuildWidgetSettingsData { enabled: boolean; - channel: TextChannel | NewsChannel | VoiceBasedChannel | ForumChannel | Snowflake | null; + channel: TextChannel | NewsChannel | VoiceBasedChannel | ForumChannel | MediaChannel | Snowflake | null; } export interface GuildSearchMembersOptions { @@ -5836,6 +5845,7 @@ export type GuildInvitableChannelResolvable = | NewsChannel | StageChannel | ForumChannel + | MediaChannel | Snowflake; export interface InviteCreateOptions { @@ -6354,11 +6364,12 @@ export type Channel = | TextChannel | AnyThreadChannel | VoiceChannel - | ForumChannel; + | ForumChannel + | MediaChannel; export type TextBasedChannel = Exclude< Extract, - PartialGroupDMChannel | ForumChannel + PartialGroupDMChannel | ForumChannel | MediaChannel >; export type TextBasedChannelTypes = TextBasedChannel['type']; @@ -6447,7 +6458,7 @@ export interface WebhookDeleteOptions { export interface WebhookEditOptions { name?: string; avatar?: BufferResolvable | null; - channel?: GuildTextChannelResolvable | VoiceChannel | ForumChannel | StageChannel; + channel?: GuildTextChannelResolvable | VoiceChannel | StageChannel | ForumChannel | MediaChannel; reason?: string; } @@ -6489,7 +6500,7 @@ export interface WidgetChannel { export interface WelcomeChannelData { description: string; - channel: TextChannel | NewsChannel | ForumChannel | Snowflake; + channel: TextChannel | NewsChannel | ForumChannel | MediaChannel | Snowflake; emoji?: EmojiIdentifierResolvable; } diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index 8dcf7f31c103..6b5f4f16f6b8 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -176,6 +176,7 @@ import { GuildOnboarding, StringSelectMenuComponentData, ButtonComponentData, + MediaChannel, } from '.'; import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd'; import type { ContextMenuCommandBuilder, SlashCommandBuilder } from '@discordjs/builders'; @@ -1324,8 +1325,8 @@ declare const user: User; declare const guildMember: GuildMember; // Test thread channels' parent inference -expectType(threadChannel.parent); -expectType(threadChannelFromForum.parent); +expectType(threadChannel.parent); +expectType(threadChannelFromForum.parent); expectType(threadChannelNotFromForum.parent); // Test whether the structures implement send @@ -1544,6 +1545,7 @@ declare const guildChannelManager: GuildChannelManager; expectType>(guildChannelManager.create({ name: 'name', type: ChannelType.GuildAnnouncement })); expectType>(guildChannelManager.create({ name: 'name', type: ChannelType.GuildStageVoice })); expectType>(guildChannelManager.create({ name: 'name', type: ChannelType.GuildForum })); + expectType>(guildChannelManager.create({ name: 'name', type: ChannelType.GuildMedia })); expectType>>(guildChannelManager.fetch()); expectType>>( @@ -1603,7 +1605,7 @@ declare const threadManager: ThreadManager; } declare const guildForumThreadManager: GuildForumThreadManager; -expectType(guildForumThreadManager.channel); +expectType(guildForumThreadManager.channel); declare const guildTextThreadManager: GuildTextThreadManager< ChannelType.PublicThread | ChannelType.PrivateThread | ChannelType.AnnouncementThread @@ -1926,6 +1928,7 @@ client.on('interactionCreate', async interaction => { expectType( interaction.options.getChannel('test', false, [ChannelType.GuildForum, ChannelType.GuildVoice]), ); + expectType(interaction.options.getChannel('test', true, [ChannelType.GuildMedia])); } else { // @ts-expect-error consumeCachedCommand(interaction); @@ -2117,7 +2120,7 @@ expectType< >(TextBasedChannelTypes); expectType(VoiceBasedChannel); expectType(GuildBasedChannel); -expectType( +expectType( NonThreadGuildBasedChannel, ); expectType(GuildTextBasedChannel);