From a62cb399414fee78204919f0158fd1e79a5670b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Conor=E2=84=A2?= <35053522+iiFDCT@users.noreply.github.com> Date: Wed, 7 Feb 2024 19:36:57 +0000 Subject: [PATCH] feat(ForumChannels): Support Forum Channels (#1452) --- esm.mjs | 1 + index.d.ts | 187 +++++++++++++++++-- index.js | 1 + lib/Client.js | 241 ++++++++++++++++++------- lib/Constants.js | 20 +- lib/gateway/Shard.js | 114 ++++++++---- lib/structures/Channel.js | 4 + lib/structures/ForumChannel.js | 176 ++++++++++++++++++ lib/structures/Guild.js | 42 +++-- lib/structures/GuildChannel.js | 80 ++++---- lib/structures/Message.js | 14 +- lib/structures/PrivateThreadChannel.js | 1 + lib/structures/PublicThreadChannel.js | 22 ++- lib/structures/TextChannel.js | 70 ++++--- lib/structures/TextVoiceChannel.js | 15 +- lib/structures/ThreadChannel.js | 46 +++-- lib/util/emitDeprecation.js | 22 +++ 17 files changed, 811 insertions(+), 245 deletions(-) create mode 100644 lib/structures/ForumChannel.js create mode 100644 lib/util/emitDeprecation.js diff --git a/esm.mjs b/esm.mjs index 46bcc17ea..c8b66aee8 100644 --- a/esm.mjs +++ b/esm.mjs @@ -21,6 +21,7 @@ export const { DiscordHTTPError, DiscordRESTError, ExtendedUser, + ForumChannel, GroupChannel, Guild, GuildChannel, diff --git a/index.d.ts b/index.d.ts index 355ced2fa..83a706a78 100644 --- a/index.d.ts +++ b/index.d.ts @@ -51,9 +51,17 @@ declare namespace Eris { // Channel type AnyChannel = AnyGuildChannel | PrivateChannel; - type AnyGuildChannel = GuildTextableChannel | AnyVoiceChannel | CategoryChannel | StoreChannel; + type AnyGuildChannel = GuildTextableChannel | AnyVoiceChannel | CategoryChannel | ForumChannel; type AnyThreadChannel = NewsThreadChannel | PrivateThreadChannel | PublicThreadChannel | ThreadChannel; type AnyVoiceChannel = TextVoiceChannel | StageChannel; + type ChannelTypeConversion = + T extends Constants["ChannelTypes"]["GUILD_TEXT"] ? TextChannel : + T extends Constants["ChannelTypes"]["GUILD_VOICE"] ? TextVoiceChannel : + T extends Constants["ChannelTypes"]["GUILD_CATEGORY"] ? CategoryChannel : + T extends Constants["ChannelTypes"]["GUILD_NEWS"] ? NewsChannel : + T extends Constants["ChannelTypes"]["GUILD_STAGE_VOICE"] ? StageChannel : + T extends Constants["ChannelTypes"]["GUILD_FORUM"] ? ForumChannel : + never; type GuildTextableChannel = TextChannel | TextVoiceChannel | NewsChannel; type GuildTextableWithThread = GuildTextableChannel | AnyThreadChannel; type InviteChannel = InvitePartialChannel | Exclude; @@ -154,8 +162,11 @@ declare namespace Eris { type StickerTypes = Constants["StickerTypes"][keyof Constants["StickerTypes"]]; type StickerFormats = Constants["StickerFormats"][keyof Constants["StickerFormats"]]; - // Thread + // Thread/Forum type AutoArchiveDuration = 60 | 1440 | 4320 | 10080; + type ChannelFlags = Constants["ChannelFlags"][keyof Constants["ChannelFlags"]]; + type ForumLayoutTypes = Constants["ForumLayoutTypes"][keyof Constants["ForumLayoutTypes"]]; + type SortOrderTypes = Constants["SortOrderTypes"][keyof Constants["SortOrderTypes"]]; // User type PremiumTypes = Constants["PremiumTypes"][keyof Constants["PremiumTypes"]]; @@ -263,6 +274,62 @@ declare namespace Eris { permissions?: ApplicationCommandPermissions[]; } + // Auto Moderation + interface AutoModerationAction { + metadata?: AutoModerationActionMetadata; + type: AutoModerationActionType; + } + interface AutoModerationActionExecution { + action: AutoModerationAction; + alertSystemMessageID?: string; + channelID?: string; + content?: string; + guildID: string; + matchedContent?: string | null; + matchedKeyword: string | null; + messageID?: string; + ruleID: string; + ruleTriggerType: AutoModerationTriggerType; + userID: string; + } + interface AutoModerationActionMetadata { + /** valid for SEND_ALERT_MESSAGE */ + channelID?: string; + /** valid for TIMEOUT */ + durationSeconds?: number; + } + interface AutoModerationRule { + actions: AutoModerationAction[]; + creatorID: string; + enabled: boolean; + eventType: AutoModerationEventType; + exemptRoles: string[]; + exemptUsers: string[]; + guildID: string; + id: string; + name: string; + triggerMetadata: AutoModerationTriggerMetadata; + triggerType: AutoModerationTriggerType; + } + interface CreateAutoModerationRuleOptions { + actions: AutoModerationAction[]; + enabled?: boolean; + eventType: AutoModerationActionType; + exemptChannels?: string[]; + exemptRoles?: string[]; + name: string; + reason?: string; + triggerMetadata?: AutoModerationTriggerMetadata; + triggerType: AutoModerationTriggerType; + } + + interface AutoModerationTriggerMetadata { + /** valid for KEYWORD */ + keywordFilter: string[]; + /** valid for KEYWORD_PRESET */ + presets: AutoModerationKeywordPresetType[]; + } + // Channel interface ChannelFollow { channel_id: string; @@ -275,7 +342,13 @@ declare namespace Eris { parentID?: string; } interface CreateChannelOptions { + availableTags?: ForumTag[]; bitrate?: number; + defaultAutoArchiveDuration?: AutoArchiveDuration; + defaultForumLayout?: ForumLayoutTypes; + defaultReactionEmoji?: DefaultReactionEmoji; + defaultSortOrder?: SortOrderTypes; + defaultThreadRateLimitPerUser?: number; nsfw?: boolean; parentID?: string; permissionOverwrites?: Overwrite[]; @@ -286,9 +359,16 @@ declare namespace Eris { userLimit?: number; } interface EditChannelOptions extends Omit { + appliedTags?: string[]; archived?: boolean; autoArchiveDuration?: AutoArchiveDuration; + availableTags?: ForumTag[]; defaultAutoArchiveDuration?: AutoArchiveDuration; + defaultForumLayout?: ForumLayoutTypes; + defaultReactionEmoji?: DefaultReactionEmoji; + defaultSortOrder?: SortOrderTypes; + defaultThreadRateLimitPerUser?: number; + flags?: number; icon?: string; invitable?: boolean; locked?: boolean; @@ -585,6 +665,14 @@ declare namespace Eris { ringing: string[]; unavailable: boolean; } + interface OldForumChannel extends OldGuildChannel { + availableTags: ForumTag[]; + defaultAutoArchiveDuration: AutoArchiveDuration; + defaultForumLayout: ForumLayoutTypes; + defaultReactionEmoji: DefaultReactionEmoji; + defaultSortOrder: SortOrderTypes; + defaultThreadRateLimitPerUser: number; + } interface OldGroupChannel { name: string; ownerID: string; @@ -632,6 +720,7 @@ declare namespace Eris { } interface OldGuildChannel { bitrate?: number; + flags?: number; name: string; nsfw?: boolean; parentID: string | null; @@ -706,6 +795,8 @@ declare namespace Eris { videoQualityMode: VideoQualityMode; } interface OldThread { + appliedTags: string[]; + autoArchiveDuration: number; name: string; rateLimitPerUser: number; threadMetadata: ThreadMetadata; @@ -731,7 +822,7 @@ declare namespace Eris { channelPinUpdate: [channel: TextableChannel, timestamp: number, oldTimestamp: number]; channelRecipientAdd: [channel: GroupChannel, user: User]; channelRecipientRemove: [channel: GroupChannel, user: User]; - channelUpdate: [channel: AnyGuildChannel, oldChannel: OldGuildChannel | OldGuildTextChannel | OldTextVoiceChannel] + channelUpdate: [channel: AnyGuildChannel, oldChannel: OldGuildChannel | OldForumChannel | OldGuildTextChannel | OldVoiceChannel] | [channel: GroupChannel, oldChannel: OldGroupChannel]; connect: [id: number]; debug: [message: string, id?: number]; @@ -1441,14 +1532,29 @@ declare namespace Eris { premium_subscriber?: true; } - // Thread + // Forum/Thread interface CreateThreadOptions { - autoArchiveDuration: AutoArchiveDuration; + autoArchiveDuration?: AutoArchiveDuration; name: string; + rateLimitPerUser?: number; + reason?: string; + } + interface CreateForumThreadOptions extends CreateThreadOptions { + appliedTags?: string[]; + message: Omit & FileContent[]; } interface CreateThreadWithoutMessageOptions extends CreateThreadOptions { - invitable: T extends PrivateThreadChannel["type"] ? boolean : never; - type: T; + invitable?: T extends PrivateThreadChannel["type"] ? boolean : never; + type?: T; + } + interface DefaultReactionEmoji { + emoji_id?: string; + emoji_name?: string; + } + interface ForumTag extends DefaultReactionEmoji { + id: string; + name: string; + moderated: boolean; } interface GetArchivedThreadsOptions { before?: Date; @@ -1693,6 +1799,10 @@ declare namespace Eris { DANGER: 4; LINK: 5; }; + ChannelFlags: { + PINNED: 1, + REQUIRE_TAG: 16 + }, ChannelTypes: { GUILD_TEXT: 0; DM: 1; @@ -1708,6 +1818,8 @@ declare namespace Eris { GUILD_STAGE_VOICE: 13; /** @deprecated */ GUILD_STAGE: 13; + + GUILD_FORUM: 15; }; ComponentTypes: { ACTION_ROW: 1; @@ -1719,10 +1831,19 @@ declare namespace Eris { NONE: 0; EVERYONE: 1; }; + ForumLayoutTypes: { + NOT_SET: 0, + LIST_VIEW: 1, + GALLERY_VIEW: 2 + }; DefaultMessageNotificationLevels: { ALL_MESSAGES: 0; ONLY_MENTIONS: 1; }; + SortOrderTypes: { + LATEST_ACTIVITY: 0, + CREATION_DATE: 1 + }; ExplicitContentFilterLevels: { DISABLED: 0; MEMBERS_WITHOUT_ROLES: 1; @@ -2392,7 +2513,7 @@ declare namespace Eris { ): Promise>; createChannelWebhook( channelID: string, - options: { name: string; avatar?: string | null }, + options: WebhookCreateOptions, reason?: string ): Promise; createCommand(command: ApplicationCommandStructure): Promise; @@ -2409,8 +2530,11 @@ declare namespace Eris { createRole(guildID: string, options?: RoleOptions, reason?: string): Promise; createRole(guildID: string, options?: Role, reason?: string): Promise; createStageInstance(channelID: string, options: StageInstanceOptions): Promise; + createThread(channelID: string, options: CreateForumThreadOptions, file?: FileContent | FileContent[]): Promise>; + createThread(channelID: string, options: CreateThreadWithoutMessageOptions, file?: FileContent | FileContent[]): Promise; createThreadWithMessage(channelID: string, messageID: string, options: CreateThreadOptions): Promise; - createThreadWithoutMessage(channelID: string, options: CreateThreadWithoutMessageOptions): Promise; + /** @deprecated */ + createThreadWithoutMessage(channelID: string, options: CreateThreadWithoutMessageOptions): Promise; crosspostMessage(channelID: string, messageID: string): Promise; deleteChannel(channelID: string, reason?: string): Promise; deleteChannelPermission(channelID: string, overwriteID: string, reason?: string): Promise; @@ -2514,7 +2638,9 @@ declare namespace Eris { /** @deprecated */ getActiveThreads(channelID: string): Promise; getArchivedThreads(channelID: string, type: "private", options?: GetArchivedThreadsOptions): Promise>; - getArchivedThreads(channelID: string, type: "public", options?: GetArchivedThreadsOptions): Promise>; + getArchivedThreads(channelID: string, type: "public", options?: GetArchivedThreadsOptions): Promise>>; + getAutoModerationRule(guildID: string, ruleID: string): Promise; + getAutoModerationRules(guildID: string): Promise; getBotGateway(): Promise<{ session_start_limit: { max_concurrency: number; remaining: number; reset_after: number; total: number }; shards: number; url: string }>; getChannel(channelID: string): AnyChannel; getChannelInvites(channelID: string): Promise; @@ -2771,6 +2897,25 @@ declare namespace Eris { verified: boolean; } + export class ForumChannel extends GuildChannel { + availableTags: ForumTag[]; + defaultAutoArchiveDuration: AutoArchiveDuration; + defaultForumLayout: ForumLayoutTypes; + defaultReactionEmoji: DefaultReactionEmoji; + defaultSortOrder: SortOrderTypes; + defaultThreadRateLimitPerUser: number; + lastMessageID: string; + rateLimitPerUser: number; + threads: PublicThreadChannel[]; + topic?: string; + createInvite(options?: CreateInviteOptions, reason?: string): Promise>; + createThread(options: CreateForumThreadOptions, file?: FileContent | FileContent[]): Promise>; + createWebhook(options: WebhookCreateOptions, reason?: string): Promise; + getArchivedThreads(options?: GetArchivedThreadsOptions): Promise>>; + getInvites(): Promise[]>; + getWebhooks(): Promise; + } + export class GroupChannel extends PrivateChannel { icon: string | null; iconURL: string | null; @@ -2982,7 +3127,7 @@ declare namespace Eris { export class GuildChannel extends Channel { guild: Guild; name: string; - nsfw: boolean; + flags?: number; parentID: string | null; permissionOverwrites: Collection; position: number; @@ -3389,7 +3534,7 @@ declare namespace Eris { crosspostMessage(messageID: string): Promise>; editMessage(messageID: string, content: MessageContentEdit): Promise>; follow(webhookChannelID: string): Promise; - getInvites(): Promise<(Invite<"withMetadata", this>)[]>; + getInvites(): Promise[]>; getMessage(messageID: string): Promise>; getMessages(options?: GetMessagesOptions): Promise[]>; /** @deprecated */ @@ -3469,7 +3614,9 @@ declare namespace Eris { type: Constants["ChannelTypes"]["GUILD_PRIVATE_THREAD"]; } - export class PublicThreadChannel extends ThreadChannel { + /** Generic T is true if the PublicThreadChannel's parent channel is a Forum Channel */ + export class PublicThreadChannel extends ThreadChannel { + appliedTags: T extends true ? string[] : never; type: GuildPublicThreadChannelTypes; edit(options: Pick, reason?: string): Promise; } @@ -3683,9 +3830,11 @@ declare namespace Eris { addMessageReaction(messageID: string, reaction: string, userID: string): Promise; createInvite(options?: CreateInviteOptions, reason?: string): Promise>; createMessage(content: MessageContent, file?: FileContent | FileContent[]): Promise>; - createThreadWithMessage(messageID: string, options: CreateThreadOptions): Promise; - createThreadWithoutMessage(options: CreateThreadWithoutMessageOptions): Promise; - createWebhook(options: { name: string; avatar?: string | null }, reason?: string): Promise; + createThread(options: CreateThreadWithoutMessageOptions): Promise; + createThreadWithMessage(messageID: string, options: CreateThreadOptions): Promise; + /** @deprecated */ + createThreadWithoutMessage(options: CreateThreadWithoutMessageOptions): Promise; + createWebhook(options: WebhookCreateOptions, reason?: string): Promise; deleteMessage(messageID: string, reason?: string): Promise; deleteMessages(messageIDs: string[], reason?: string): Promise; edit(options: Omit, reason?: string): Promise; @@ -3694,7 +3843,7 @@ declare namespace Eris { getActiveThreads(): Promise; getArchivedThreads(type: "private", options?: GetArchivedThreadsOptions): Promise>; getArchivedThreads(type: "public", options?: GetArchivedThreadsOptions): Promise>; - getInvites(): Promise<(Invite<"withMetadata", this>)[]>; + getInvites(): Promise[]>; getJoinedPrivateArchivedThreads(options: GetArchivedThreadsOptions): Promise>; getMessage(messageID: string): Promise>; getMessageReaction(messageID: string, reaction: string, options?: GetMessageReactionOptions): Promise; @@ -3726,8 +3875,10 @@ declare namespace Eris { messageCount: number; messages: Collection>; ownerID: string; + parentID: string; rateLimitPerUser: number; threadMetadata: ThreadMetadata; + totalMessageSent: number; type: GuildThreadChannelTypes; constructor(data: BaseData, client: Client, messageLimit?: number); addMessageReaction(messageID: string, reaction: string): Promise; @@ -3811,7 +3962,7 @@ declare namespace Eris { videoQualityMode: VideoQualityMode; voiceMembers: Collection; createInvite(options?: CreateInviteOptions, reason?: string): Promise>; - getInvites(): Promise<(Invite<"withMetadata", VoiceChannel>)[]>; + getInvites(): Promise[]>; join(options?: JoinVoiceChannelOptions): Promise; leave(): void; } diff --git a/index.js b/index.js index 7aa68567b..d592e0124 100644 --- a/index.js +++ b/index.js @@ -22,6 +22,7 @@ Eris.Constants = require("./lib/Constants"); Eris.DiscordHTTPError = require("./lib/errors/DiscordHTTPError"); Eris.DiscordRESTError = require("./lib/errors/DiscordRESTError"); Eris.ExtendedUser = require("./lib/structures/ExtendedUser"); +Eris.ForumChannel = require("./lib/structures/ForumChannel"); Eris.GroupChannel = require("./lib/structures/GroupChannel"); Eris.Guild = require("./lib/structures/Guild"); Eris.GuildChannel = require("./lib/structures/GuildChannel"); diff --git a/lib/Client.js b/lib/Client.js index 9504da95a..d250dc5fd 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -499,22 +499,28 @@ class Client extends EventEmitter { } /** - * Create a channel in a guild - * @arg {String} guildID The ID of the guild to create the channel in - * @arg {String} name The name of the channel - * @arg {String} [type=0] The type of the channel, either 0 (text), 2 (voice), 4 (category), 5 (news), 6 (store), or 13 (stage) - * @arg {Object | String} [options] The properties the channel should have. If `options` is a string, it will be treated as `options.parentID` (see below). Passing a string is deprecated and will not be supported in future versions. - * @arg {Number} [options.bitrate] The bitrate of the channel (voice channels only) - * @arg {Boolean} [options.nsfw] The nsfw status of the channel - * @arg {String?} [options.parentID] The ID of the parent category channel for this channel - * @arg {Array} [options.permissionOverwrites] An array containing permission overwrite objects - * @arg {Number} [options.position] The sorting position of the channel - * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (does not affect bots or users with manageMessages/manageChannel permissions) (text channels only) - * @arg {String} [options.reason] The reason to be displayed in audit logs - * @arg {String} [options.topic] The topic of the channel (text channels only) - * @arg {Number} [options.userLimit] The channel user limit (voice channels only) - * @returns {Promise} - */ + * Create a channel in a guild + * @arg {String} guildID The ID of the guild to create the channel in + * @arg {String} name The name of the channel + * @arg {Number} [type=0] The type of the channel, either 0 (text), 2 (voice), 4 (category), 5 (news), 13 (stage), or 15 (forum) + * @arg {Object | String} [options] The properties the channel should have. If `options` is a string, it will be treated as `options.parentID` (see below). Passing a string is deprecated and will not be supported in future versions. + * @arg {Array} [options.availableTags] The available tags that can be applied to threads in a forum channel. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#forum-tag-object) for object structure (forum channels only, max 20) + * @arg {Number} [options.bitrate] The bitrate of the channel (voice channels only) + * @arg {Number} [options.defaultAutoArchiveDuration] The default duration of newly created threads in minutes to automatically archive the thread after inactivity (60, 1440, 4320, 10080) (text/news/forum channels only) + * @arg {Number} [options.defaultForumLayout] The default forum layout type used to display posts in forum channels (forum channels only) + * @arg {Object} [options.defaultReactionEmoji] The emoji to show in the add reaction button on a thread in a forum channel (forum channels only) + * @arg {Number} [options.defaultSortOrder] The default sort order type used to order posts in forum channels (forum channels only) + * @arg {Number} [options.defaultThreadRateLimitPerUser] The initial rateLimitPerUser to set on newly created threads in a channel (text/forum channels only) + * @arg {Boolean} [options.nsfw] The nsfw status of the channel + * @arg {String?} [options.parentID] The ID of the parent category channel for this channel + * @arg {Array} [options.permissionOverwrites] An array containing permission overwrite objects + * @arg {Number} [options.position] The sorting position of the channel + * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) (text/voice/stage/forum channels only) + * @arg {String} [options.reason] The reason to be displayed in audit logs + * @arg {String} [options.topic] The topic of the channel (text channels only) + * @arg {Number} [options.userLimit] The channel user limit (voice channels only) + * @returns {Promise} + */ createChannel(guildID, name, type, reason, options = {}) { if(typeof options === "string") { // This used to be parentID, back-compat this.emit("warn", "[DEPRECATED] createChannel() was called with a string `options` argument"); @@ -533,7 +539,13 @@ class Client extends EventEmitter { return this.requestHandler.request("POST", Endpoints.GUILD_CHANNELS(guildID), true, { name: name, type: type, + available_tags: options.availableTags, bitrate: options.bitrate, + default_auto_archive_duration: options.defaultAutoArchiveDuration, + default_forum_layout: options.defaultForumLayout, + default_reaction_emoji: options.defaultReactionEmoji, + default_sort_order: options.defaultSortOrder, + default_thread_rate_limit_per_user: options.defaultThreadRateLimitPerUser, nsfw: options.nsfw, parent_id: options.parentID, permission_overwrites: options.permissionOverwrites, @@ -964,32 +976,107 @@ class Client extends EventEmitter { } /** - * Create a thread with an existing message - * @arg {String} channelID The ID of the channel - * @arg {String} messageID The ID of the message to create the thread from - * @arg {Object} options The thread options - * @arg {Number} options.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 - * @arg {String} options.name The thread channel name - * @returns {Promise} - */ + * Create a thread in a channel + * @arg {String} channelID The ID of the channel + * @arg {Object} options The thread options + * @arg {Array} [options.appliedTags] The IDs of the set of tags that have been applied to a thread in a forum channel (threads created in forum channels only, max 5) + * @arg {Number} [options.autoArchiveDuration] The duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 + * @arg {Boolean} [options.invitable] Whether non-moderators can add other non-moderators to the thread (private threads only) + * @arg {Object} [options.message] The message to create with the thread (only use when creating a thread inside of a `GUILD_FORUM` channel). Note: When creating a forum channel thread, you must provide at least one of `content`, `embeds`, `stickerIDs`, `components`, or `files` + * @arg {Object} [options.message.allowedMentions] A list of mentions to allow (overrides default) + * @arg {Boolean} [options.message.allowedMentions.everyone] Whether or not to allow @everyone/@here + * @arg {Boolean} [options.message.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to + * @arg {Boolean | Array} [options.message.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow + * @arg {Boolean | Array} [options.message.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow + * @arg {Array} [options.message.attachments] An array of attachment objects with the filename and description + * @arg {String} [options.message.attachments[].description] The description of the file + * @arg {String} [options.message.attachments[].filename] The name of the file + * @arg {Number} options.message.attachments[].id The index of the file + * @arg {Array} [options.message.components] An array of component objects + * @arg {String} [options.message.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only) + * @arg {Boolean} [options.message.components[].disabled] Whether the component is disabled (type 2 and 3 only) + * @arg {Object} [options.message.components[].emoji] The emoji to be displayed in the component (type 2) + * @arg {String} [options.message.components[].label] The label to be displayed in the component (type 2) + * @arg {Number} [options.message.components[].max_values] The maximum number of items that can be chosen (1-25, default 1) + * @arg {Number} [options.message.components[].min_values] The minimum number of items that must be chosen (0-25, default 1) + * @arg {Array} [options.message.components[].options] The options for this component (type 3 only) + * @arg {Boolean} [options.message.components[].options[].default] Whether this option should be the default value selected + * @arg {String} [options.message.components[].options[].description] The description for this option + * @arg {Object} [options.message.components[].options[].emoji] The emoji to be displayed in this option + * @arg {String} options.message.components[].options[].label The label for this option + * @arg {Number | String} options.message.components[].options[].value The value for this option + * @arg {String} [options.message.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only) + * @arg {Number} [options.message.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required + * @arg {Number} options.message.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu + * @arg {String} [options.message.components[].url] The URL that the component should open for users (type 2 style 5 only) + * @arg {String} [options.message.content] A string containing the message content + * @arg {Array} [options.message.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure + * @arg {Array} [options.message.stickerIDs] An array of IDs corresponding to stickers to send + * @arg {String} options.name The thread channel name + * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) + * @arg {String} [options.reason] The reason to be displayed in audit logs + * @arg {Number} [options.type] The channel type of the thread to create. Either `10` (announcement thread, announcement channels only), `11` (public thread) or `12` (private thread). Note: Not required when creating a thread inside of a forum channel, it will always be public + * @arg {Object | Array} [file] A file object (or an Array of them). Only use when creating a thread inside of a forum channel + * @arg {Buffer} file.file A buffer containing file data + * @arg {String} file.name What to name the file + * @returns {Promise} + */ + createThread(channelID, options, file) { + if(options.message) { + options.message.allowed_mentions = this._formatAllowedMentions(options.message.allowedMentions); + options.message.sticker_ids = options.message.stickerIDs; + } + let payload = { + applied_tags: options.appliedTags, + auto_archive_duration: options.autoArchiveDuration, + invitable: options.invitable, + message: options.message, + name: options.name, + rate_limit_per_user: options.rateLimitPerUser, + reason: options.reason, + type: options.type + }; + if(file) { + payload = {payload_json: payload}; + } + return this.requestHandler.request("POST", Endpoints.THREAD_WITHOUT_MESSAGE(channelID), true, payload, file).then((channel) => Channel.from(channel, this)); + } + + /** + * Create a thread with an existing message + * @arg {String} channelID The ID of the channel + * @arg {String} messageID The ID of the message to create the thread from + * @arg {Object} options The thread options + * @arg {Number} [options.autoArchiveDuration] Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 + * @arg {String} options.name The thread channel name + * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) + * @arg {String} [options.reason] The reason to be displayed in audit logs + * @returns {Promise} + */ createThreadWithMessage(channelID, messageID, options) { return this.requestHandler.request("POST", Endpoints.THREAD_WITH_MESSAGE(channelID, messageID), true, { + auto_archive_duration: options.autoArchiveDuration, name: options.name, - auto_archive_duration: options.autoArchiveDuration + rate_limit_per_user: options.rateLimitPerUser, + reason: options.reason }).then((channel) => Channel.from(channel, this)); } /** - * Create a thread without an existing message - * @arg {String} channelID The ID of the channel - * @arg {Object} options The thread options - * @arg {Number} options.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 - * @arg {Boolean} [options.invitable] Whether non-moderators can add other non-moderators to the thread (private threads only) - * @arg {String} options.name The thread channel name - * @arg {Number} [options.type] The channel type of the thread to create. It is recommended to explicitly set this property as this will be a required property in API v10 - * @returns {Promise} - */ + * [DEPRECATED] Create a thread without an existing message. Use `createThread` instead + * @arg {String} channelID The ID of the channel + * @arg {Object} options The thread options + * @arg {Number} [options.autoArchiveDuration] Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 + * @arg {Boolean} [options.invitable] Whether non-moderators can add other non-moderators to the thread (private threads only) + * @arg {String} options.name The thread channel name + * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) + * @arg {String} [options.reason] The reason to be displayed in audit logs + * @arg {Number} [options.type] The channel type of the thread to create. Either `10` (announcement thread, announcement channels only), `11` (public thread) or `12` (private thread) + * @returns {Promise} + */ createThreadWithoutMessage(channelID, options) { + emitDeprecation("CREATE_THREAD_WITHOUT_MESSAGE"); + this.emit("warn", "[DEPRECATED] createThreadWithoutMessage() is deprecated. Use createThread() instead."); return this.requestHandler.request("POST", Endpoints.THREAD_WITHOUT_MESSAGE(channelID), true, { auto_archive_duration: options.autoArchiveDuration, invitable: options.invitable, @@ -1308,36 +1395,50 @@ class Client extends EventEmitter { } /** - * Edit a channel's properties - * @arg {String} channelID The ID of the channel - * @arg {Object} options The properties to edit - * @arg {Boolean} [options.archived] The archive status of the channel (thread channels only) - * @arg {Number} [options.autoArchiveDuration] The duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 (thread channels only) - * @arg {Number} [options.bitrate] The bitrate of the channel (guild voice channels only) - * @arg {Number?} [options.defaultAutoArchiveDuration] The default duration of newly created threads in minutes to automatically archive the thread after inactivity (60, 1440, 4320, 10080) (guild text/news channels only) - * @arg {String} [options.icon] The icon of the channel as a base64 data URI (group channels only). Note: base64 strings alone are not base64 data URI strings - * @arg {Boolean} [options.invitable] Whether non-moderators can add other non-moderators to the channel (private thread channels only) - * @arg {Boolean} [options.locked] The lock status of the channel (thread channels only) - * @arg {String} [options.name] The name of the channel - * @arg {Boolean} [options.nsfw] The nsfw status of the channel (guild channels only) - * @arg {String} [options.ownerID] The ID of the channel owner (group channels only) - * @arg {String?} [options.parentID] The ID of the parent channel category for this channel (guild text/voice channels only) - * @arg {Array} [options.permissionOverwrites] An array containing permission overwrite objects - * @arg {Number} [options.position] The sorting position of the channel (guild channels only) - * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (does not affect bots or users with manageMessages/manageChannel permissions) (guild text and thread channels only) - * @arg {String?} [options.rtcRegion] The RTC region ID of the channel (automatic if `null`) (guild voice channels only) - * @arg {String} [options.topic] The topic of the channel (guild text channels only) - * @arg {Number} [options.userLimit] The channel user limit (guild voice channels only) - * @arg {Number} [options.videoQualityMode] The camera video quality mode of the channel (guild voice channels only). `1` is auto, `2` is 720p - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} - */ + * Edit a channel's properties + * @arg {String} channelID The ID of the channel + * @arg {Object} options The properties to edit + * @arg {Array} [options.appliedTags] The IDs of the set of tags that have been applied to a thread in a forum channel (threads created in forum channels only, max 5) + * @arg {Boolean} [options.archived] The archive status of the channel (thread channels only) + * @arg {Number} [options.autoArchiveDuration] The duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 (thread channels only) + * @arg {Array} [options.availableTags] The available tags that can be applied to threads in a forum channel. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#forum-tag-object) for object structure (forum channels only, max 20) + * @arg {Number} [options.bitrate] The bitrate of the channel (guild voice channels only) + * @arg {Number} [options.defaultAutoArchiveDuration] The default duration of newly created threads in minutes to automatically archive the thread after inactivity (60, 1440, 4320, 10080) (text/news/forum channels only) + * @arg {Number} [options.defaultForumLayout] The default forum layout type used to display posts in forum channels (forum channels only) + * @arg {Object} [options.defaultReactionEmoji] The emoji to show in the add reaction button on a thread in a forum channel (forum channels only) + * @arg {Number} [options.defaultSortOrder] The default sort order type used to order posts in forum channels (forum channels only) + * @arg {Number} [options.defaultThreadRateLimitPerUser] The initial rateLimitPerUser to set on newly created threads in a channel (text/forum channels only) + * @arg {Number} [options.flags] The flags for the channel combined as a bitfield (thread/forum channels only). Note: `PINNED` can only be set for threads in forum channels, and `REQUIRE_TAG` can only be set for forum channels + * @arg {String} [options.icon] The icon of the channel as a base64 data URI (group channels only). Note: base64 strings alone are not base64 data URI strings + * @arg {Boolean} [options.invitable] Whether non-moderators can add other non-moderators to the channel (private thread channels only) + * @arg {Boolean} [options.locked] The lock status of the channel (thread channels only) + * @arg {String} [options.name] The name of the channel + * @arg {Boolean} [options.nsfw] The nsfw status of the channel (guild channels only) + * @arg {String} [options.ownerID] The ID of the channel owner (group channels only) + * @arg {String?} [options.parentID] The ID of the parent channel category for this channel (guild text/voice channels only) + * @arg {Array} [options.permissionOverwrites] An array containing permission overwrite objects + * @arg {Number} [options.position] The sorting position of the channel (guild channels only) + * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) (text/voice/stage/forum channels only) + * @arg {String?} [options.rtcRegion] The RTC region ID of the channel (automatic if `null`) (guild voice channels only) + * @arg {String} [options.topic] The topic of the channel (guild text channels only) + * @arg {Number} [options.userLimit] The channel user limit (guild voice channels only) + * @arg {Number} [options.videoQualityMode] The camera video quality mode of the channel (guild voice channels only). `1` is auto, `2` is 720p + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ editChannel(channelID, options, reason) { return this.requestHandler.request("PATCH", Endpoints.CHANNEL(channelID), true, { + applied_tags: options.appliedTags, archived: options.archived, auto_archive_duration: options.autoArchiveDuration, + available_tags: options.availableTags, bitrate: options.bitrate, default_auto_archive_duration: options.defaultAutoArchiveDuration, + default_forum_layout: options.defaultForumLayout, + default_reaction_emoji: options.defaultReactionEmoji, + default_sort_order: options.defaultSortOrder, + default_thread_rate_limit_per_user: options.defaultThreadRateLimitPerUser, + flags: options.flags, icon: options.icon, invitable: options.invitable, locked: options.locked, @@ -2282,10 +2383,10 @@ class Client extends EventEmitter { } /** - * Get a Channel object from a channel ID - * @arg {String} channelID The ID of the channel - * @returns {CategoryChannel | GroupChannel | PrivateChannel | TextChannel | TextVoiceChannel | NewsChannel | NewsThreadChannel | PrivateThreadChannel | PublicThreadChannel} - */ + * Get a Channel object from a channel ID + * @arg {String} channelID The ID of the channel + * @returns {CategoryChannel | ForumChannel | GroupChannel | NewsChannel | NewsThreadChannel | PrivateChannel | PrivateThreadChannel | PublicThreadChannel | TextChannel | TextVoiceChannel} + */ getChannel(channelID) { if(!channelID) { throw new Error(`Invalid channel ID: ${channelID}`); @@ -2860,10 +2961,10 @@ class Client extends EventEmitter { } /** - * Get a channel's data via the REST API. REST mode is required to use this endpoint. - * @arg {String} channelID The ID of the channel - * @returns {Promise} - */ + * Get a channel's data via the REST API. REST mode is required to use this endpoint. + * @arg {String} channelID The ID of the channel + * @returns {Promise} + */ getRESTChannel(channelID) { if(!this.options.restMode) { return Promise.reject(new Error("Eris REST mode is not enabled")); @@ -2888,10 +2989,10 @@ class Client extends EventEmitter { } /** - * Get a guild's channels via the REST API. REST mode is required to use this endpoint. - * @arg {String} guildID The ID of the guild - * @returns {Promise | Array | Array | Array>} - */ + * Get a guild's channels via the REST API. REST mode is required to use this endpoint. + * @arg {String} guildID The ID of the guild + * @returns {Promise} + */ getRESTGuildChannels(guildID) { if(!this.options.restMode) { return Promise.reject(new Error("Eris REST mode is not enabled")); diff --git a/lib/Constants.js b/lib/Constants.js index 1bf772389..baedf148a 100644 --- a/lib/Constants.js +++ b/lib/Constants.js @@ -108,6 +108,11 @@ module.exports.ButtonStyles = { LINK: 5 }; +module.exports.ChannelFlags = { + PINNED: 1 << 1, + REQUIRE_TAG: 1 << 4 +}; + module.exports.ChannelTypes = { GUILD_TEXT: 0, DM: 1, @@ -121,8 +126,8 @@ module.exports.ChannelTypes = { GUILD_PUBLIC_THREAD: 11, GUILD_PRIVATE_THREAD: 12, GUILD_STAGE_VOICE: 13, GUILD_STAGE: 13, // [DEPRECATED] - GUILD_DIRECTORY: 14, - GUILD_FORUM: 15, + + GUILD_FORUM: 15 }; module.exports.ComponentTypes = { @@ -142,11 +147,22 @@ module.exports.ConnectionVisibilityTypes = { EVERYONE: 1 }; +module.exports.ForumLayoutTypes = { + NOT_SET: 0, + LIST_VIEW: 1, + GALLERY_VIEW: 2 +}; + module.exports.DefaultMessageNotificationLevels = { ALL_MESSAGES: 0, ONLY_MENTIONS: 1 }; +module.exports.SortOrderTypes = { + LATEST_ACTIVITY: 0, + CREATION_DATE: 1 +}; + module.exports.ExplicitContentFilterLevels = { DISABLED: 0, MEMBERS_WITHOUT_ROLES: 1, diff --git a/lib/gateway/Shard.js b/lib/gateway/Shard.js index f5045de09..d7f67fbfd 100644 --- a/lib/gateway/Shard.js +++ b/lib/gateway/Shard.js @@ -5,6 +5,7 @@ const Base = require("../structures/Base"); const Bucket = require("../util/Bucket"); const Call = require("../structures/Call"); const Channel = require("../structures/Channel"); +const ForumChannel = require("../structures/ForumChannel"); const GroupChannel = require("../structures/GroupChannel"); const GuildChannel = require("../structures/GuildChannel"); const Message = require("../structures/Message"); @@ -859,6 +860,10 @@ class Shard extends EventEmitter { const channel = this.client.getChannel(packet.d.channel_id); if(channel) { // MESSAGE_CREATE just when deleting o.o channel.lastMessageID = packet.d.id; + if(channel instanceof ThreadChannel) { + channel.messageCount++; + channel.totalMessageSent++; + } /** * Fired when a message is created * @event Client#messageCreate @@ -922,7 +927,9 @@ class Shard extends EventEmitter { } case "MESSAGE_DELETE": { const channel = this.client.getChannel(packet.d.channel_id); - + if(channel instanceof ThreadChannel) { + channel.messageCount--; + } /** * Fired when a cached message is deleted * @event Client#messageDelete @@ -940,7 +947,9 @@ class Shard extends EventEmitter { } case "MESSAGE_DELETE_BULK": { const channel = this.client.getChannel(packet.d.channel_id); - + if(channel instanceof ThreadChannel) { + channel.messageCount -= packet.d.ids.length; + } /** * Fired when a bulk delete occurs * @event Client#messageDeleteBulk @@ -1530,10 +1539,10 @@ class Shard extends EventEmitter { channel.guild.channels.add(channel, this.client); this.client.channelGuildMap[packet.d.id] = packet.d.guild_id; /** - * Fired when a channel is created - * @event Client#channelCreate - * @prop {TextChannel | TextVoiceChannel | CategoryChannel | StoreChannel | NewsChannel | GuildChannel} channel The channel - */ + * Fired when a channel is created + * @event Client#channelCreate + * @prop {ForumChannel | TextChannel | TextVoiceChannel | CategoryChannel | NewsChannel | GuildChannel} channel The channel + */ this.emit("channelCreate", channel); } else { this.emit("warn", new Error("Unhandled CHANNEL_CREATE type: " + JSON.stringify(packet, null, 2))); @@ -1555,7 +1564,14 @@ class Shard extends EventEmitter { }; } else if(channel instanceof GuildChannel) { oldChannel = { + availableTags: channel.availableTags, bitrate: channel.bitrate, + defaultAutoArchiveDuration: channel.defaultAutoArchiveDuration, + defaultForumLayout: channel.defaultForumLayout, + defaultReactionEmoji: channel.defaultReactionEmoji, + defaultSortOrder: channel.defaultSortOrder, + defaultThreadRateLimitPerUser: channel.defaultThreadRateLimitPerUser, + flags: channel.flags, name: channel.name, nsfw: channel.nsfw, parentID: channel.parentID, @@ -1601,23 +1617,30 @@ class Shard extends EventEmitter { } /** - * Fired when a channel is updated - * @event Client#channelUpdate - * @prop {TextChannel | TextVoiceChannel | CategoryChannel | StoreChannel | NewsChannel | GuildChannel | PrivateChannel} channel The updated channel - * @prop {Object} oldChannel The old channel data - * @prop {Number} oldChannel.bitrate The bitrate of the channel (voice channels only) - * @prop {String} oldChannel.name The name of the channel - * @prop {Boolean} oldChannel.nsfw Whether the channel is NSFW or not (text channels only) - * @prop {String?} oldChannel.parentID The ID of the category this channel belongs to (guild channels only) - * @prop {Collection} oldChannel.permissionOverwrites Collection of PermissionOverwrites in this channel (guild channels only) - * @prop {Number} oldChannel.position The position of the channel (guild channels only) - * @prop {Number?} oldChannel.rateLimitPerUser The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled (text channels only) - * @prop {String?} oldChannel.rtcRegion The RTC region ID of the channel (automatic when `null`) (voice channels only) - * @prop {String?} oldChannel.topic The topic of the channel (text channels only) - * @prop {Number} oldChannel.type The type of the old channel (text/news channels only) - * @prop {Number?} oldChannel.userLimit The max number of users that can join the channel (voice channels only) - * @prop {Number?} oldChannel.videoQualityMode The camera video quality mode of the channel (voice channels only) - */ + * Fired when a channel is updated + * @event Client#channelUpdate + * @prop {ForumChannel | TextChannel | TextVoiceChannel | CategoryChannel | NewsChannel | GuildChannel | PrivateChannel} channel The updated channel + * @prop {Object} oldChannel The old channel data + * @prop {Array} oldChannel.availableTags The available tags that can be applied to threads in a forum channel. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#forum-tag-object) for object structure (forum channels only, max 20) + * @prop {Number} oldChannel.bitrate The bitrate of the channel (voice channels only) + * @prop {Number} oldChannel.defaultAutoArchiveDuration The default duration of newly created threads in minutes to automatically archive the thread after inactivity (60, 1440, 4320, 10080) (text/news/forum channels only) + * @prop {Number} oldChannel.defaultForumLayout The default forum layout type used to display posts in forum channels (forum channels only) + * @prop {Object} oldChannel.defaultReactionEmoji The emoji to show in the add reaction button on a thread in a forum channel (forum channels only) + * @prop {Number} oldChannel.defaultSortOrder The default sort order type used to order posts in forum channels (forum channels only) + * @prop {Number} oldChannel.defaultThreadRateLimitPerUser The initial rateLimitPerUser to set on newly created threads in a channel (text/forum channels only) + * @prop {Number?} oldChannel.flags The flags for the channel combined as a bitfield (thread/forum channels only) + * @prop {String} oldChannel.name The name of the channel + * @prop {Boolean} oldChannel.nsfw Whether the channel is NSFW or not (text channels only) + * @prop {String?} oldChannel.parentID The ID of the category this channel belongs to (guild channels only) + * @prop {Collection} oldChannel.permissionOverwrites Collection of PermissionOverwrites in this channel (guild channels only) + * @prop {Number} oldChannel.position The position of the channel (guild channels only) + * @prop {Number?} oldChannel.rateLimitPerUser The time in seconds a user has to wait before sending another message (0-21600) (text/voice/stage/forum channels only) + * @prop {String?} oldChannel.rtcRegion The RTC region ID of the channel (automatic when `null`) (voice channels only) + * @prop {String?} oldChannel.topic The topic of the channel (text channels only) + * @prop {Number} oldChannel.type The type of the old channel (text/news channels only) + * @prop {Number?} oldChannel.userLimit The max number of users that can join the channel (voice channels only) + * @prop {Number?} oldChannel.videoQualityMode The camera video quality mode of the channel (voice channels only) + */ this.emit("channelUpdate", channel, oldChannel); break; } @@ -1628,10 +1651,10 @@ class Shard extends EventEmitter { if(channel) { delete this.client.privateChannelMap[channel.recipient.id]; /** - * Fired when a channel is deleted - * @event Client#channelDelete - * @prop {PrivateChannel | TextChannel | NewsChannel | TextVoiceChannel | CategoryChannel} channel The channel - */ + * Fired when a channel is deleted + * @event Client#channelDelete + * @prop {ForumChannel | PrivateChannel | TextChannel | NewsChannel | TextVoiceChannel | CategoryChannel} channel The channel + */ this.emit("channelDelete", channel); } } @@ -2157,6 +2180,13 @@ class Shard extends EventEmitter { } channel.guild.threads.add(channel, this.client); this.client.threadGuildMap[packet.d.id] = packet.d.guild_id; + + const parent = channel.guild.channels.get(channel.parentID); + + if(parent instanceof ForumChannel) { + parent.lastMessageID = channel.id; + } + /** * Fired when a channel is created * @event Client#threadCreate @@ -2178,6 +2208,9 @@ class Shard extends EventEmitter { break; } const oldChannel = { + appliedTags: channel.appliedTags, + autoArchiveDuration: channel.autoArchiveDuration, + flags: channel.flags, name: channel.name, rateLimitPerUser: channel.rateLimitPerUser, threadMetadata: channel.threadMetadata @@ -2185,18 +2218,21 @@ class Shard extends EventEmitter { channel.update(packet.d); /** - * Fired when a thread channel is updated - * @event Client#threadUpdate - * @prop {NewsThreadChannel | PrivateThreadChannel | PublicThreadChannel} channel The updated channel - * @prop {Object?} oldChannel The old thread channel. This will be null if the channel was uncached - * @prop {String} oldChannel.name The name of the channel - * @prop {Number} oldChannel.rateLimitPerUser The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled - * @prop {Object} oldChannel.threadMetadata Metadata for the thread - * @prop {Number} oldChannel.threadMetadata.archiveTimestamp Timestamp when the thread's archive status was last changed, used for calculating recent activity - * @prop {Boolean} oldChannel.threadMetadata.archived Whether the thread is archived - * @prop {Number} oldChannel.threadMetadata.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 - * @prop {Boolean?} oldChannel.threadMetadata.locked Whether the thread is locked - */ + * Fired when a thread channel is updated + * @event Client#threadUpdate + * @prop {NewsThreadChannel | PrivateThreadChannel | PublicThreadChannel} channel The updated channel + * @prop {Object?} oldChannel The old thread channel. This will be null if the channel was uncached + * @prop {Array?} oldChannel.appliedTags The IDs of the set of tags that have been applied to a thread in a forum channel + * @prop {Number} oldChannel.autoArchiveDuration The duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 + * @prop {Number} oldChannel.flags The flags for the channel combined as a bitfield + * @prop {String} oldChannel.name The name of the channel + * @prop {Number} oldChannel.rateLimitPerUser The time in seconds a user has to wait before sending another message (0-21600) + * @prop {Object} oldChannel.threadMetadata Metadata for the thread + * @prop {Boolean} oldChannel.threadMetadata.archived Whether the thread is archived + * @prop {Number} oldChannel.threadMetadata.archiveTimestamp Timestamp when the thread's archive status was last changed, used for calculating recent activity + * @prop {Number} oldChannel.threadMetadata.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 + * @prop {Boolean?} oldChannel.threadMetadata.locked Whether the thread is locked + */ this.emit("threadUpdate", channel, oldChannel); break; } diff --git a/lib/structures/Channel.js b/lib/structures/Channel.js index f11098ead..bccd75fa2 100644 --- a/lib/structures/Channel.js +++ b/lib/structures/Channel.js @@ -57,6 +57,9 @@ class Channel extends Base { case ChannelTypes.GUILD_STAGE_VOICE: { return new StageChannel(data, client); } + case ChannelTypes.GUILD_FORUM: { + return new ForumChannel(data, client); + } } if(data.guild_id) { if(data.last_message_id !== undefined) { @@ -83,6 +86,7 @@ module.exports = Channel; // Circular import const CategoryChannel = require("./CategoryChannel"); const GuildChannel = require("./GuildChannel"); +const ForumChannel = require("./ForumChannel"); const GroupChannel = require("./GroupChannel"); const NewsChannel = require("./NewsChannel"); const NewsThreadChannel = require("./NewsThreadChannel"); diff --git a/lib/structures/ForumChannel.js b/lib/structures/ForumChannel.js new file mode 100644 index 000000000..71276c94e --- /dev/null +++ b/lib/structures/ForumChannel.js @@ -0,0 +1,176 @@ +"use strict"; + +const GuildChannel = require("./GuildChannel"); + +/** + * Represents a guild forum channel. See GuildChannel for more properties and methods. + * @extends GuildChannel + * @prop {Array} availableTags The available tags that can be applied to threads in the forum channel. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#forum-tag-object) for object structure (max 20) + * @prop {Number} defaultAutoArchiveDuration The default duration of newly created threads in minutes to automatically archive the thread after inactivity (60, 1440, 4320, 10080) + * @prop {Number} defaultForumLayout The default forum layout type used to display posts in the forum channel + * @prop {Object} defaultReactionEmoji The emoji to show in the add reaction button on a thread in the forum channel + * @prop {Number} defaultSortOrder The default sort order type used to order posts in the forum channel + * @prop {Number} defaultThreadRateLimitPerUser The initial rateLimitPerUser to set on newly created threads in the forum channel + * @prop {String} lastMessageID The ID of the most recently created thread in the forum channel + * @prop {Number} rateLimitPerUser The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) + * @prop {Array} threads An array of threads in the forum channel + * @prop {String?} topic The topic of the channel + */ +class ForumChannel extends GuildChannel { + constructor(data, client) { + super(data, client); + this.lastMessageID = data.last_message_id || null; + this.rateLimitPerUser = data.rate_limit_per_user == null ? null : data.rate_limit_per_user; + this.update(data); + } + + update(data) { + super.update(data); + if(data.topic !== undefined) { + this.topic = data.topic; + } + if(data.available_tags !== undefined) { + this.availableTags = data.available_tags; + } + if(data.default_auto_archive_duration !== undefined) { + this.defaultAutoArchiveDuration = data.default_auto_archive_duration; + } + if(data.default_forum_layout !== undefined) { + this.defaultForumLayout = data.default_forum_layout; + } + if(data.default_reaction_emoji !== undefined) { + this.defaultReactionEmoji = data.default_reaction_emoji; + } + if(data.default_sort_order !== undefined) { + this.defaultSortOrder = data.default_sort_order; + } + if(data.default_thread_rate_limit_per_user !== undefined) { + this.defaultThreadRateLimitPerUser = data.default_thread_rate_limit_per_user; + } + if(data.rate_limit_per_user !== undefined) { + this.rateLimitPerUser = data.rate_limit_per_user; + } + } + + get threads() { + return this.guild.threads.filter((thread) => thread.parentID === this.id); + } + + /** + * Create an invite for the channel + * @arg {Object} [options] Invite generation options + * @arg {Number} [options.maxAge] How long the invite should last in seconds + * @arg {Number} [options.maxUses] How many uses the invite should last for + * @arg {Boolean} [options.temporary] Whether the invite grants temporary membership or not + * @arg {Boolean} [options.unique] Whether the invite is unique or not + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ + createInvite(options, reason) { + return this.client.createChannelInvite.call(this.client, this.id, options, reason); + } + + /** + * Create a thread inside the forum channel + * @arg {Object} options The thread options + * @arg {Array} [options.appliedTags] The IDs of the set of tags that have been applied to a thread in a forum channel (threads created in forum channels only, max 5) + * @arg {Number} [options.autoArchiveDuration] The duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 (thread channels only) + * @arg {Object} [options.message] The message to create with the thread. Note: When creating a forum channel thread, you must provide at least one of `content`, `embeds`, `stickerIDs`, `components`, or `files` + * @arg {Object} [options.message.allowedMentions] A list of mentions to allow (overrides default) + * @arg {Boolean} [options.message.allowedMentions.everyone] Whether or not to allow @everyone/@here + * @arg {Boolean} [options.message.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to + * @arg {Boolean | Array} [options.message.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow + * @arg {Boolean | Array} [options.message.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow + * @arg {Array} [options.message.attachments] An array of attachment objects with the filename and description + * @arg {String} [options.message.attachments[].description] The description of the file + * @arg {String} [options.message.attachments[].filename] The name of the file + * @arg {Number} options.message.attachments[].id The index of the file + * @arg {Array} [options.message.components] An array of component objects + * @arg {String} [options.message.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only) + * @arg {Boolean} [options.message.components[].disabled] Whether the component is disabled (type 2 and 3 only) + * @arg {Object} [options.message.components[].emoji] The emoji to be displayed in the component (type 2) + * @arg {String} [options.message.components[].label] The label to be displayed in the component (type 2) + * @arg {Number} [options.message.components[].max_values] The maximum number of items that can be chosen (1-25, default 1) + * @arg {Number} [options.message.components[].min_values] The minimum number of items that must be chosen (0-25, default 1) + * @arg {Array} [options.message.components[].options] The options for this component (type 3 only) + * @arg {Boolean} [options.message.components[].options[].default] Whether this option should be the default value selected + * @arg {String} [options.message.components[].options[].description] The description for this option + * @arg {Object} [options.message.components[].options[].emoji] The emoji to be displayed in this option + * @arg {String} options.message.components[].options[].label The label for this option + * @arg {Number | String} options.message.components[].options[].value The value for this option + * @arg {String} [options.message.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only) + * @arg {Number} [options.message.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required + * @arg {Number} options.message.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu + * @arg {String} [options.message.components[].url] The URL that the component should open for users (type 2 style 5 only) + * @arg {String} [options.message.content] A string containing the message content + * @arg {Array} [options.message.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure + * @arg {Array} [options.message.stickerIDs] An array of IDs corresponding to stickers to send + * @arg {String} options.name The thread channel name + * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) + * @arg {String} [options.reason] The reason to be displayed in audit logs + * @arg {Object | Array} [file] A file object (or an Array of them) + * @arg {Buffer} file.file A buffer containing file data + * @arg {String} file.name What to name the file + * @returns {Promise} + */ + createThread(options, file) { + return this.client.createThread.call(this.client, this.id, options, file); + } + + /** + * Create a channel webhook + * @arg {Object} options Webhook options + * @arg {String} [options.avatar] The default avatar as a base64 data URI. Note: base64 strings alone are not base64 data URI strings + * @arg {String} options.name The default name + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} Resolves with a webhook object + */ + createWebhook(options, reason) { + return this.client.createChannelWebhook.call(this.client, this.id, options, reason); + } + + /** + * Get all archived threads in this channel + * @arg {Object} [options] Additional options when requesting archived threads + * @arg {Date} [options.before] List of threads to return before the timestamp + * @arg {Number} [options.limit] Maximum number of threads to return + * @returns {Promise} An object containing an array of `threads`, an array of `members` and whether the response `hasMore` threads that could be returned in a subsequent call + */ + getArchivedThreads(options) { + return this.client.getArchivedThreads.call(this.client, this.id, "public", options); + } + + /** + * Get all invites in the channel + * @returns {Promise>} + */ + getInvites() { + return this.client.getChannelInvites.call(this.client, this.id); + } + + /** + * Get all the webhooks in the channel + * @returns {Promise>} Resolves with an array of webhook objects + */ + getWebhooks() { + return this.client.getChannelWebhooks.call(this.client, this.id); + } + + toJSON(props = []) { + return super.toJSON([ + "availableTags", + "defaultAutoArchiveDuration", + "defaultForumLayout", + "defaultReactionEmoji", + "defaultSortOrder", + "defaultThreadRateLimitPerUser", + "nsfw", + "position", + "rateLimitPerUser", + "topic", + ...props + ]); + } +} + +module.exports = ForumChannel; diff --git a/lib/structures/Guild.js b/lib/structures/Guild.js index 8efdb1df6..87ed05593 100644 --- a/lib/structures/Guild.js +++ b/lib/structures/Guild.js @@ -401,21 +401,27 @@ class Guild extends Base { } /** - * Create a channel in the guild - * @arg {String} name The name of the channel - * @arg {Number} [type=0] The type of the channel, either 0 (text), 2 (voice), 4 (category), 5 (news) or 13 (stage) - * @arg {Object | String} [options] The properties the channel should have. If `options` is a string, it will be treated as `options.parentID` (see below). Passing a string is deprecated and will not be supported in future versions. - * @arg {Number} [options.bitrate] The bitrate of the channel (voice channels only) - * @arg {Boolean} [options.nsfw] The nsfw status of the channel - * @arg {String?} [options.parentID] The ID of the parent category channel for this channel - * @arg {Array} [options.permissionOverwrites] An array containing permission overwrite objects - * @arg {Number} [options.position] The sorting position of the channel - * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (does not affect bots or users with manageMessages/manageChannel permissions) (text channels only) - * @arg {String} [options.reason] The reason to be displayed in audit logs - * @arg {String} [options.topic] The topic of the channel (text channels only) - * @arg {Number} [options.userLimit] The channel user limit (voice channels only) - * @returns {Promise} - */ + * Create a channel in the guild + * @arg {String} name The name of the channel + * @arg {Number} [type=0] The type of the channel, either 0 (text), 2 (voice), 4 (category), 5 (news), 13 (stage), or 15 (forum) + * @arg {Object | String} [options] The properties the channel should have. If `options` is a string, it will be treated as `options.parentID` (see below). Passing a string is deprecated and will not be supported in future versions. + * @arg {Array} [options.availableTags] The available tags that can be applied to threads in a forum channel. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#forum-tag-object) for object structure (forum channels only, max 20) + * @arg {Number} [options.bitrate] The bitrate of the channel (voice channels only) + * @arg {Number} [options.defaultAutoArchiveDuration] The default duration of newly created threads in minutes to automatically archive the thread after inactivity (60, 1440, 4320, 10080) (text/news/forum channels only) + * @arg {Number} [options.defaultForumLayout] The default forum layout type used to display posts in forum channels (forum channels only) + * @arg {Object} [options.defaultReactionEmoji] The emoji to show in the add reaction button on a thread in a forum channel (forum channels only) + * @arg {Number} [options.defaultSortOrder] The default sort order type used to order posts in forum channels (forum channels only) + * @arg {Number} [options.defaultThreadRateLimitPerUser] The initial rateLimitPerUser to set on newly created threads in a channel (text/forum channels only) + * @arg {Boolean} [options.nsfw] The nsfw status of the channel + * @arg {String?} [options.parentID] The ID of the parent category channel for this channel + * @arg {Array} [options.permissionOverwrites] An array containing permission overwrite objects + * @arg {Number} [options.position] The sorting position of the channel + * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) (text/voice/stage/forum channels only) + * @arg {String} [options.reason] The reason to be displayed in audit logs + * @arg {String} [options.topic] The topic of the channel (text channels only) + * @arg {Number} [options.userLimit] The channel user limit (voice channels only) + * @returns {Promise} + */ createChannel(name, type, reason, options) { return this._client.createChannel.call(this._client, this.id, name, type, reason, options); } @@ -1027,9 +1033,9 @@ class Guild extends Base { } /** - * Get a guild's channels via the REST API. REST mode is required to use this endpoint. - * @returns {Promise | Array | Array>} - */ + * Get a guild's channels via the REST API. REST mode is required to use this endpoint. + * @returns {Promise | TextVoiceChannel>} + */ getRESTChannels() { return this._client.getRESTGuildChannels.call(this._client, this.id); } diff --git a/lib/structures/GuildChannel.js b/lib/structures/GuildChannel.js index 2476d3f66..7155fc326 100644 --- a/lib/structures/GuildChannel.js +++ b/lib/structures/GuildChannel.js @@ -7,16 +7,16 @@ const {Permissions} = require("../Constants"); const PermissionOverwrite = require("./PermissionOverwrite"); /** -* Represents a guild channel. You also probably want to look at CategoryChannel, NewsChannel, StoreChannel, TextChannel, and TextVoiceChannel. See Channel for extra properties. -* @extends Channel -* @prop {Guild} guild The guild that owns the channel -* @prop {String} id The ID of the channel -* @prop {String} name The name of the channel -* @prop {Boolean} nsfw Whether the channel is an NSFW channel or not -* @prop {String?} parentID The ID of the category this channel belongs to or the channel ID where the thread originated from (thread channels only) -* @prop {Collection} permissionOverwrites Collection of PermissionOverwrites in this channel -* @prop {Number} position The position of the channel -*/ + * Represents a guild channel. You also probably want to look at CategoryChannel, NewsChannel, TextChannel, ThreadChannel, and TextVoiceChannel. See Channel for extra properties. + * @extends Channel + * @prop {Number?} flags Channel flags (see constants) (thread/forum channels only) + * @prop {Guild} guild The guild that owns the channel + * @prop {String} id The ID of the channel + * @prop {String} name The name of the channel + * @prop {String?} parentID The ID of the category this channel belongs to or the channel ID where the thread originated from (thread channels only) + * @prop {Collection} permissionOverwrites Collection of PermissionOverwrites in this channel + * @prop {Number} position The position of the channel + */ class GuildChannel extends Channel { constructor(data, client) { super(data, client); @@ -40,12 +40,8 @@ class GuildChannel extends Channel { if(data.parent_id !== undefined) { this.parentID = data.parent_id; } - this.nsfw = data.nsfw; - if(data.permission_overwrites) { - this.permissionOverwrites = new Collection(PermissionOverwrite); - data.permission_overwrites.forEach((overwrite) => { - this.permissionOverwrites.add(overwrite); - }); + if(data.flags !== undefined) { + this.flags = data.flags; } } @@ -69,27 +65,34 @@ class GuildChannel extends Channel { } /** - * Edit the channel's properties - * @arg {Object} options The properties to edit - * @arg {Boolean} [options.archived] The archive status of the channel (thread channels only) - * @arg {Number} [options.autoArchiveDuration] The duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 (thread channels only) - * @arg {Number} [options.bitrate] The bitrate of the channel (guild voice channels only) - * @arg {Number?} [options.defaultAutoArchiveDuration] The default duration of newly created threads in minutes to automatically archive the thread after inactivity (60, 1440, 4320, 10080) (guild text/news channels only) - * @arg {Boolean} [options.invitable] Whether non-moderators can add other non-moderators to the channel (private thread channels only) - * @arg {Boolean} [options.locked] The lock status of the channel (thread channels only) - * @arg {String} [options.name] The name of the channel - * @arg {Boolean} [options.nsfw] The nsfw status of the channel - * @arg {Number?} [options.parentID] The ID of the parent channel category for this channel (guild text/voice channels only) or the channel ID where the thread originated from (thread channels only) - * @arg {Array} [options.permissionOverwrites] An array containing permission overwrite objects - * @arg {Number} [options.position] The sorting position of the channel - * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (does not affect bots or users with manageMessages/manageChannel permissions) (guild text and thread channels only) - * @arg {String?} [options.rtcRegion] The RTC region ID of the channel (automatic if `null`) (guild voice channels only) - * @arg {String} [options.topic] The topic of the channel (guild text channels only) - * @arg {Number} [options.userLimit] The channel user limit (guild voice channels only) - * @arg {Number} [options.videoQualityMode] The camera video quality mode of the channel (guild voice channels only). `1` is auto, `2` is 720p - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} - */ + * Edit the channel's properties + * @arg {Object} options The properties to edit + * @arg {Array} [options.appliedTags] The IDs of the set of tags that have been applied to a thread in a forum channel (threads created in forum channels only, max 5) + * @arg {Boolean} [options.archived] The archive status of the channel (thread channels only) + * @arg {Number} [options.autoArchiveDuration] The duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 (thread channels only) + * @arg {Array} [options.availableTags] The available tags that can be applied to threads in a forum channel. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#forum-tag-object) for object structure (forum channels only, max 20) + * @arg {Number} [options.bitrate] The bitrate of the channel (guild voice channels only) + * @arg {Number} [options.defaultAutoArchiveDuration] The default duration of newly created threads in minutes to automatically archive the thread after inactivity (60, 1440, 4320, 10080) (text/news/forum channels only) + * @arg {Number} [options.defaultForumLayout] The default forum layout type used to display posts in forum channels (forum channels only) + * @arg {Object} [options.defaultReactionEmoji] The emoji to show in the add reaction button on a thread in a forum channel (forum channels only) + * @arg {Number} [options.defaultSortOrder] The default sort order type used to order posts in forum channels (forum channels only) + * @arg {Number} [options.defaultThreadRateLimitPerUser] The initial rateLimitPerUser to set on newly created threads in a channel (text/forum channels only) + * @arg {Number} [options.flags] The flags for the channel combined as a bitfield (thread/forum channels only). Note: `PINNED` can only be set for threads in forum channels, and `REQUIRE_TAG` can only be set for forum channels + * @arg {Boolean} [options.invitable] Whether non-moderators can add other non-moderators to the channel (private thread channels only) + * @arg {Boolean} [options.locked] The lock status of the channel (thread channels only) + * @arg {String} [options.name] The name of the channel + * @arg {Boolean} [options.nsfw] The nsfw status of the channel + * @arg {String?} [options.parentID] The ID of the parent channel category for this channel (guild text/voice channels only) or the channel ID where the thread originated from (thread channels only) + * @arg {Array} [options.permissionOverwrites] An array containing permission overwrite objects + * @arg {Number} [options.position] The sorting position of the channel + * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) (text/voice/stage/forum channels only) + * @arg {String?} [options.rtcRegion] The RTC region ID of the channel (automatic if `null`) (guild voice channels only) + * @arg {String} [options.topic] The topic of the channel (guild text channels only) + * @arg {Number} [options.userLimit] The channel user limit (guild voice channels only) + * @arg {Number} [options.videoQualityMode] The camera video quality mode of the channel (guild voice channels only). `1` is auto, `2` is 720p + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ edit(options, reason) { return this.client.editChannel.call(this.client, this.id, options, reason); } @@ -156,8 +159,7 @@ class GuildChannel extends Channel { "name", "nsfw", "parentID", - "permissionOverwrites", - "position", + "flags", ...props ]); } diff --git a/lib/structures/Message.js b/lib/structures/Message.js index 76a400344..124affe0c 100644 --- a/lib/structures/Message.js +++ b/lib/structures/Message.js @@ -394,12 +394,14 @@ class Message extends Base { } /** - * Create a thread with this message - * @arg {Object} options The thread options - * @arg {Number} options.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 - * @arg {String} options.name The thread channel name - * @returns {Promise} - */ + * Create a thread with this message + * @arg {Object} options The thread options + * @arg {Number} [options.autoArchiveDuration] Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 + * @arg {String} options.name The thread channel name + * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) + * @arg {String} [options.reason] The reason to be displayed in audit logs + * @returns {Promise} + */ createThreadWithMessage(options) { return this._client.createThreadWithMessage.call(this._client, this.channel.id, this.id, options); } diff --git a/lib/structures/PrivateThreadChannel.js b/lib/structures/PrivateThreadChannel.js index 69643e6d2..d099e03ee 100644 --- a/lib/structures/PrivateThreadChannel.js +++ b/lib/structures/PrivateThreadChannel.js @@ -19,6 +19,7 @@ class PrivateThreadChannel extends ThreadChannel { } update(data) { + super.update(data); if(data.thread_metadata !== undefined) { this.threadMetadata = { archiveTimestamp: Date.parse(data.thread_metadata.archive_timestamp), diff --git a/lib/structures/PublicThreadChannel.js b/lib/structures/PublicThreadChannel.js index 62cf74a86..6138fd139 100644 --- a/lib/structures/PublicThreadChannel.js +++ b/lib/structures/PublicThreadChannel.js @@ -3,12 +3,28 @@ const ThreadChannel = require("./ThreadChannel"); /** -* Represents a public thread channel. See ThreadChannel for extra properties. -* @extends ThreadChannel -*/ + * Represents a public thread channel. See ThreadChannel for extra properties. + * @extends ThreadChannel + * @prop {Array?} appliedTags The IDs of the set of tags that have been applied to a thread in a forum channel (threads created in forum channels only, max 5) + */ class PublicThreadChannel extends ThreadChannel { constructor(data, client, messageLimit) { super(data, client, messageLimit); + this.update(data); + } + + update(data) { + super.update(data); + if(data.applied_tags !== undefined) { + this.appliedTags = data.applied_tags; + } + } + + toJSON(props = []) { + return super.toJSON([ + "appliedTags", + ...props + ]); } } diff --git a/lib/structures/TextChannel.js b/lib/structures/TextChannel.js index 01e3c8a68..14b662f41 100644 --- a/lib/structures/TextChannel.js +++ b/lib/structures/TextChannel.js @@ -5,15 +5,18 @@ const GuildChannel = require("./GuildChannel"); const Message = require("./Message"); /** -* Represents a guild text channel. See GuildChannel for more properties and methods. -* @extends GuildChannel -* @prop {Number} defaultAutoArchiveDuration The default duration of newly created threads in minutes to automatically archive the thread after inactivity (60, 1440, 4320, 10080) -* @prop {String} lastMessageID The ID of the last message in this channel -* @prop {Number} lastPinTimestamp The timestamp of the last pinned message -* @prop {Collection} messages Collection of Messages in this channel -* @prop {Number} rateLimitPerUser The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled -* @prop {String?} topic The topic of the channel -*/ + * Represents a guild text channel. See GuildChannel for more properties and methods. + * @extends GuildChannel + * @prop {Number} defaultAutoArchiveDuration The default duration of newly created threads in minutes to automatically archive the thread after inactivity (60, 1440, 4320, 10080) + * @prop {String} lastMessageID The ID of the last message in this channel + * @prop {Number} lastPinTimestamp The timestamp of the last pinned message + * @prop {Collection} messages Collection of Messages in this channel + * @prop {Boolean} nsfw Whether the channel is an NSFW channel or not + * @prop {Collection} permissionOverwrites Collection of PermissionOverwrites in this channel + * @prop {Number} position The position of the channel + * @prop {Number} rateLimitPerUser The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) + * @prop {String?} topic The topic of the channel + */ class TextChannel extends GuildChannel { constructor(data, client, messageLimit) { super(data, client); @@ -108,26 +111,45 @@ class TextChannel extends GuildChannel { } /** - * Create a thread with an existing message - * @arg {String} messageID The ID of the message to create the thread from - * @arg {Object} options The thread options - * @arg {Number} options.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 - * @arg {String} options.name The thread channel name - * @returns {Promise} - */ + * Create a thread without an existing message + * @arg {Object} options The thread options + * @arg {Number} [options.autoArchiveDuration] The duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 (thread channels only) + * @arg {Boolean} [options.invitable] Whether non-moderators can add other non-moderators to the thread (private threads only) + * @arg {String} options.name The thread channel name + * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) + * @arg {String} [options.reason] The reason to be displayed in audit logs + * @arg {Number} [options.type] The channel type of the thread to create. Either `10` (announcement thread, announcement channels only), `11` (public thread) or `12` (private thread) + * @returns {Promise} + */ + createThread(options) { + return this.client.createThread.call(this.client, this.id, options); + } + + /** + * Create a thread with an existing message + * @arg {String} messageID The ID of the message to create the thread from + * @arg {Object} options The thread options + * @arg {Number} [options.autoArchiveDuration] Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 + * @arg {String} options.name The thread channel name + * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) + * @arg {String} [options.reason] The reason to be displayed in audit logs + * @returns {Promise} + */ createThreadWithMessage(messageID, options) { return this.client.createThreadWithMessage.call(this.client, this.id, messageID, options); } /** - * Create a thread without an existing message - * @arg {Object} options The thread options - * @arg {Number} options.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 - * @arg {boolean} [options.invitable] Whether non-moderators can add other non-moderators to the thread (private threads only) - * @arg {String} options.name The thread channel name - * @arg {Number} [options.type] The channel type of the thread to create. It is recommended to explicitly set this property as this will be a required property in API v10 - * @returns {Promise} - */ + * [DEPRECATED] Create a thread without an existing message. Use `createThread` instead + * @arg {Object} options The thread options + * @arg {Number} [options.autoArchiveDuration] Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 + * @arg {Boolean} [options.invitable] Whether non-moderators can add other non-moderators to the thread (private threads only) + * @arg {String} options.name The thread channel name + * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) + * @arg {String} [options.reason] The reason to be displayed in audit logs + * @arg {Number} [options.type] The channel type of the thread to create. Either `10` (announcement thread, announcement channels only), `11` (public thread) or `12` (private thread) + * @returns {Promise} + */ createThreadWithoutMessage(options) { return this.client.createThreadWithoutMessage.call(this.client, this.id, options); } diff --git a/lib/structures/TextVoiceChannel.js b/lib/structures/TextVoiceChannel.js index ffa1f8f11..da152cc7b 100644 --- a/lib/structures/TextVoiceChannel.js +++ b/lib/structures/TextVoiceChannel.js @@ -5,12 +5,15 @@ const Collection = require("../util/Collection"); const Message = require("./Message"); /** -* Represents a Text-in-Voice channel. See VoiceChannel for more properties and methods. -* @extends VoiceChannel -* @prop {String} lastMessageID The ID of the last message in this channel -* @prop {Collection} messages Collection of Messages in this channel -* @prop {Number} rateLimitPerUser The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled -*/ + * Represents a Text-in-Voice channel. See VoiceChannel for more properties and methods. + * @extends VoiceChannel + * @prop {String} lastMessageID The ID of the last message in this channel + * @prop {Collection} messages Collection of Messages in this channel + * @prop {Boolean} nsfw Whether the channel is an NSFW channel or not + * @prop {Number} rateLimitPerUser The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) + * @prop {Number?} userLimit The max number of users that can join the channel + * @prop {Number?} videoQualityMode The camera video quality mode of the voice channel. `1` is auto, `2` is 720p + */ class TextVoiceChannel extends VoiceChannel { constructor(data, client, messageLimit) { super(data, client); diff --git a/lib/structures/ThreadChannel.js b/lib/structures/ThreadChannel.js index 4a0a015a0..d0dcdc1f0 100644 --- a/lib/structures/ThreadChannel.js +++ b/lib/structures/ThreadChannel.js @@ -6,26 +6,28 @@ const Message = require("./Message"); const ThreadMember = require("./ThreadMember"); /** -* Represents a thread channel. You also probably want to look at NewsThreadChannel, PublicThreadChannel, and PrivateThreadChannel. See GuildChannel for extra properties. -* @extends GuildChannel -* @prop {String} lastMessageID The ID of the last message in this channel -* @prop {Object?} member Thread member for the current user, if they have joined the thread -* @prop {Number} member.flags The user's thread settings -* @prop {String} member.id The ID of the thread -* @prop {Number} member.joinTimestamp The time the user last joined the thread -* @prop {String} member.userID The ID of the user -* @prop {Number} memberCount An approximate number of users in the thread (stops at 50) -* @prop {Collection} members Collection of members in this channel -* @prop {Number} messageCount An approximate number of messages in the thread (stops at 50) -* @prop {Collection} messages Collection of Messages in this channel -* @prop {String} ownerID The ID of the user that created the thread -* @prop {Number} rateLimitPerUser The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled -* @prop {Object} threadMetadata Metadata for the thread -* @prop {Number} threadMetadata.archiveTimestamp Timestamp when the thread's archive status was last changed, used for calculating recent activity -* @prop {Boolean} threadMetadata.archived Whether the thread is archived -* @prop {Number} threadMetadata.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 -* @prop {Boolean} threadMetadata.locked Whether the thread is locked -*/ + * Represents a thread channel. You also probably want to look at NewsThreadChannel, PublicThreadChannel, and PrivateThreadChannel. See GuildChannel for extra properties. + * @extends GuildChannel + * @prop {String} lastMessageID The ID of the last message in this channel + * @prop {Object?} member Thread member for the current user, if they have joined the thread + * @prop {Number} member.flags The user's thread settings + * @prop {String} member.id The ID of the thread + * @prop {Number} member.joinTimestamp The time the user last joined the thread + * @prop {String} member.userID The ID of the user + * @prop {Number} memberCount An approximate number of users in the thread (stops at 50) + * @prop {Collection} members Collection of members in this channel + * @prop {Number} messageCount An approximate number of messages in the thread + * @prop {Collection} messages Collection of Messages in this channel + * @prop {String} ownerID The ID of the user that created the thread + * @prop {Number} rateLimitPerUser The time in seconds a user has to wait before sending another message (0-21600) (does not affect bots or users with manageMessages/manageChannel permissions) + * @prop {Object} threadMetadata Metadata for the thread + * @prop {Boolean} threadMetadata.archived Whether the thread is archived + * @prop {Number} threadMetadata.archiveTimestamp Timestamp when the thread's archive status was last changed, used for calculating recent activity + * @prop {Number} threadMetadata.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 + * @prop {Number?} threadMetadata.createTimestamp Timestamp when the thread was created (only available for threads created after 09 January 2022) + * @prop {Boolean} threadMetadata.locked Whether the thread is locked + * @prop {Number} totalMessageSent Total number of messages ever sent in a thread (similar to `messageCount`, however this number will not decrement when a message is deleted) + */ class ThreadChannel extends GuildChannel { constructor(data, client, messageLimit) { super(data, client); @@ -58,6 +60,9 @@ class ThreadChannel extends GuildChannel { if(data.member !== undefined) { this.member = new ThreadMember(data.member, this.client); } + if(data.total_message_sent !== undefined) { + this.totalMessageSent = data.total_message_sent; + } } /** @@ -336,6 +341,7 @@ class ThreadChannel extends GuildChannel { "rateLimitPerUser", "threadMetadata", "member", + "totalMessageSent", ...props ]); } diff --git a/lib/util/emitDeprecation.js b/lib/util/emitDeprecation.js new file mode 100644 index 000000000..67b0d6af4 --- /dev/null +++ b/lib/util/emitDeprecation.js @@ -0,0 +1,22 @@ +const warningMessages = { + CREATE_CHANNEL_OPTIONS: "Passing parentID or reason string arguments directly to createChannel() is deprecated. Use an options object instead.", + CREATE_THREAD_WITHOUT_MESSAGE: "createThreadWithoutMessage() is deprecated. Use createThread() instead.", + DEFAULT_PERMISSION: "Passing a defaultPermission for application commands is deprecated. Use defaultMemberPermissions instead.", + DELETE_MESSAGE_DAYS: "Passing the deleteMessageDays parameter to banGuildMember is deprecated. Use an options object with deleteMessageSeconds instead.", + DM_REACTION_REMOVE: "Passing a userID when using removeMessageReaction() in a PrivateChannel is deprecated. This behavior is no longer supported by Discord.", + GET_REACTION_BEFORE: "Passing the before parameter to getMessageReaction() is deprecated. Discord no longer supports this parameter.", + MEMBER_PERMISSION: "Member#permission is deprecated. Use Member#permissions instead.", + MESSAGE_REFERENCE: "Passing the content.messageReferenceID option to createMessage() is deprecated. Use the content.messageReference option instead.", + REACTION_USER: "Passing a userID other than \"@me\" to addMessageReaction() is deprecated. Discord no longer supports this parameter." +}; +const unknownCodeMessage = "You have triggered a deprecated behavior whose warning was implemented improperly. Please report this issue."; + +const emittedCodes = []; + +module.exports = function emitDeprecation(code) { + if(emittedCodes.includes(code) ) { + return; + } + emittedCodes.push(code); + process.emitWarning(warningMessages[code] || unknownCodeMessage, "DeprecationWarning", `eris:${code}`); +};