From 8959991b2bbbcf156ab6ae38f9fdc0bb3aa43d6e Mon Sep 17 00:00:00 2001 From: Jiralite <33201955+Jiralite@users.noreply.github.com> Date: Tue, 20 Jun 2023 19:21:58 +0100 Subject: [PATCH] feat: add media channels --- packages/discord.js/src/index.js | 2 + .../src/structures/BaseForumChannel.js | 248 ++++++++++++++++++ .../discord.js/src/structures/ForumChannel.js | 241 +---------------- .../discord.js/src/structures/MediaChannel.js | 11 + packages/discord.js/src/util/Channels.js | 5 + packages/discord.js/typings/index.d.ts | 17 +- 6 files changed, 283 insertions(+), 241 deletions(-) create mode 100644 packages/discord.js/src/structures/BaseForumChannel.js create mode 100644 packages/discord.js/src/structures/MediaChannel.js diff --git a/packages/discord.js/src/index.js b/packages/discord.js/src/index.js index 3c0dcd96319b..4bc417f27b4d 100644 --- a/packages/discord.js/src/index.js +++ b/packages/discord.js/src/index.js @@ -97,6 +97,7 @@ exports.AutocompleteInteraction = require('./structures/AutocompleteInteraction' exports.AutoModerationActionExecution = require('./structures/AutoModerationActionExecution'); exports.AutoModerationRule = require('./structures/AutoModerationRule'); exports.Base = require('./structures/Base'); +exports.BaseForumChannel = require('./structures/BaseForumChannel'); exports.BaseGuild = require('./structures/BaseGuild'); exports.BaseGuildEmoji = require('./structures/BaseGuildEmoji'); exports.BaseGuildTextChannel = require('./structures/BaseGuildTextChannel'); @@ -147,6 +148,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'); diff --git a/packages/discord.js/src/structures/BaseForumChannel.js b/packages/discord.js/src/structures/BaseForumChannel.js new file mode 100644 index 000000000000..4735fc18498e --- /dev/null +++ b/packages/discord.js/src/structures/BaseForumChannel.js @@ -0,0 +1,248 @@ +'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 base symbols shared between forum-like channels. + * @extends {GuildChannel} + * @implements {TextBasedChannel} + */ +class BaseForumChannel 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(BaseForumChannel, true, [ + 'send', + 'lastMessage', + 'lastPinAt', + 'bulkDelete', + 'sendTyping', + 'createMessageCollector', + 'awaitMessages', + 'createMessageComponentCollector', + 'awaitMessageComponent', +]); + +module.exports = BaseForumChannel; diff --git a/packages/discord.js/src/structures/ForumChannel.js b/packages/discord.js/src/structures/ForumChannel.js index 87e647820424..6e62b492e039 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 BaseForumChannel = require('./BaseForumChannel'); /** - * @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 {BaseForumChannel} */ - -/** - * @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 BaseForumChannel { _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/MediaChannel.js b/packages/discord.js/src/structures/MediaChannel.js new file mode 100644 index 000000000000..54e2a414a912 --- /dev/null +++ b/packages/discord.js/src/structures/MediaChannel.js @@ -0,0 +1,11 @@ +'use strict'; + +const BaseForumChannel = require('./BaseForumChannel'); + +/** + * Represents a media channel. + * @extends {BaseForumChannel} + */ +class MediaChannel extends BaseForumChannel {} + +module.exports = MediaChannel; diff --git a/packages/discord.js/src/util/Channels.js b/packages/discord.js/src/util/Channels.js index 8d070bd53ddd..237e31e96b71 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,10 @@ function createChannel(client, data, guild, { allowUnknownGuild } = {}) { case ChannelType.GuildForum: channel = new (getForumChannel())(guild, data, client); break; + // TODO: ChannelType.GuildMedia + case 16: + channel = new (getMediaChannel())(guild, data, client); + break; } if (channel && !allowUnknownGuild) guild.channels?.cache.set(channel.id, channel); } diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 766ff2deb2be..6b9db4b3f138 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -2375,7 +2375,7 @@ export interface DefaultReactionEmoji { name: string | null; } -export class ForumChannel extends TextBasedChannelMixin(GuildChannel, true, [ +export class BaseForumChannel extends TextBasedChannelMixin(GuildChannel, true, [ 'send', 'lastMessage', 'lastPinAt', @@ -2386,7 +2386,8 @@ export class ForumChannel extends TextBasedChannelMixin(GuildChannel, true, [ 'createMessageComponentCollector', 'awaitMessageComponent', ]) { - public type: ChannelType.GuildForum; + // @ts-expect-error: discord-api-types. + public type: ChannelType.GuildForum | ChannelType.GuildMedia; public threads: GuildForumThreadManager; public availableTags: GuildForumTag[]; public defaultReactionEmoji: DefaultReactionEmoji | null; @@ -2396,8 +2397,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; @@ -2409,9 +2408,19 @@ 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 BaseForumChannel { + public type: ChannelType.GuildForum; + public defaultForumLayout: ForumLayoutType; public setDefaultForumLayout(defaultForumLayout: ForumLayoutType, reason?: string): Promise; } +export class MediaChannel extends ForumChannel { + // @ts-expect-error: discord-api-types. + public type: ChannelType.GuildMedia; +} + export class PermissionOverwrites extends Base { private constructor(client: Client, data: RawPermissionOverwriteData, channel: NonThreadGuildBasedChannel); public allow: Readonly;