From a6932546e2ed919bf130a919a597b4f31ef35307 Mon Sep 17 00:00:00 2001 From: ckohen Date: Sun, 29 Aug 2021 05:23:01 -0700 Subject: [PATCH] feat(Threads): add support for invitable in private threads (#6501) Co-authored-by: Rodry <38259440+ImRodry@users.noreply.github.com> Co-authored-by: SpaceEEC Co-authored-by: Noel --- src/errors/Messages.js | 1 + src/managers/ThreadManager.js | 5 ++++- src/structures/ThreadChannel.js | 24 ++++++++++++++++++++++++ typings/index.d.ts | 8 +++++++- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/errors/Messages.js b/src/errors/Messages.js index e631bb3ab4a8..f99dd2d97724 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -99,6 +99,7 @@ const Messages = { MESSAGE_THREAD_PARENT: 'The message was not sent in a guild text or news channel', MESSAGE_EXISTING_THREAD: 'The message already has a thread', + THREAD_INVITABLE_TYPE: type => `Invitable cannot be edited on ${type}`, WEBHOOK_MESSAGE: 'The message was not sent by a webhook.', WEBHOOK_TOKEN_UNAVAILABLE: 'This action requires a webhook token, but none is available.', diff --git a/src/managers/ThreadManager.js b/src/managers/ThreadManager.js index 654c86c218a9..f3935849c688 100644 --- a/src/managers/ThreadManager.js +++ b/src/managers/ThreadManager.js @@ -78,6 +78,8 @@ class ThreadManager extends CachedManager { * @property {ThreadChannelTypes|number} [type] The type of thread to create. Defaults to `GUILD_PUBLIC_THREAD` if * created in a {@link TextChannel} When creating threads in a {@link NewsChannel} this is ignored and is always * `GUILD_NEWS_THREAD` + * @property {boolean} [invitable] Whether non-moderators can add other non-moderators to the thread + * Can only be set when type will be `GUILD_PRIVATE_THREAD` */ /** @@ -106,7 +108,7 @@ class ThreadManager extends CachedManager { * .then(threadChannel => console.log(threadChannel)) * .catch(console.error); */ - async create({ name, autoArchiveDuration, startMessage, type, reason } = {}) { + async create({ name, autoArchiveDuration, startMessage, type, invitable, reason } = {}) { let path = this.client.api.channels(this.channel.id); if (type && typeof type !== 'string' && typeof type !== 'number') { throw new TypeError('INVALID_TYPE', 'type', 'ThreadChannelType or Number'); @@ -134,6 +136,7 @@ class ThreadManager extends CachedManager { name, auto_archive_duration: autoArchiveDuration, type: resolvedType, + invitable: resolvedType === ChannelTypes.GUILD_PRIVATE_THREAD ? invitable : undefined, }, reason, }); diff --git a/src/structures/ThreadChannel.js b/src/structures/ThreadChannel.js index 4d09d46094b6..02e82d0fb454 100644 --- a/src/structures/ThreadChannel.js +++ b/src/structures/ThreadChannel.js @@ -2,6 +2,7 @@ const Channel = require('./Channel'); const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const { RangeError } = require('../errors'); const MessageManager = require('../managers/MessageManager'); const ThreadMemberManager = require('../managers/ThreadMemberManager'); const Permissions = require('../util/Permissions'); @@ -77,6 +78,13 @@ class ThreadChannel extends Channel { */ this.locked = data.thread_metadata.locked ?? false; + /** + * Whether members without `MANAGE_THREADS` can invite other members without `MANAGE_THREADS` + * Always `null` in public threads + * @type {?boolean} + */ + this.invitable = this.type === 'GUILD_PRIVATE_THREAD' ? data.thread_metadata.invitable ?? false : null; + /** * Whether the thread is archived * @type {?boolean} @@ -109,6 +117,7 @@ class ThreadChannel extends Channel { if (!this.archiveTimestamp) { this.archiveTimestamp = null; } + this.invitable ??= null; } if ('owner_id' in data) { @@ -273,6 +282,8 @@ class ThreadChannel extends Channel { * should automatically archive in case of no recent activity * @property {number} [rateLimitPerUser] The ratelimit per user for the thread in seconds * @property {boolean} [locked] Whether the thread is locked + * @property {boolean} [invitable] Whether non-moderators can add other non-moderators to a thread + * Can only be edited on `GUILD_PRIVATE_THREAD` */ /** @@ -303,6 +314,7 @@ class ThreadChannel extends Channel { auto_archive_duration: autoArchiveDuration, rate_limit_per_user: data.rateLimitPerUser, locked: data.locked, + invitable: this.type === 'GUILD_PRIVATE_THREAD' ? data.invitable : undefined, }, reason, }); @@ -343,6 +355,18 @@ class ThreadChannel extends Channel { return this.edit({ autoArchiveDuration }, reason); } + /** + * Sets whether members without the `MANAGE_THREADS` permission can invite other members without the + * `MANAGE_THREADS` permission to this thread. + * @param {boolean} [invitable=true] Whether non-moderators can invite non-moderators to this thread + * @param {string} [reason] Reason for changing invite + * @returns {Promise} + */ + setInvitable(invitable = true, reason) { + if (this.type !== 'GUILD_PRIVATE_THREAD') return Promise.reject(new RangeError('THREAD_INVITABLE_TYPE', this.type)); + return this.edit({ invitable }, reason); + } + /** * Sets whether the thread can be **unarchived** by anyone with `SEND_MESSAGES` permission. * When a thread is locked only members with `MANAGE_THREADS` can unarchive it. diff --git a/typings/index.d.ts b/typings/index.d.ts index 6006d10491b8..24a3638afb09 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -306,7 +306,7 @@ export class BaseGuildTextChannel extends TextBasedChannel(GuildChannel) { public defaultAutoArchiveDuration?: ThreadAutoArchiveDuration; public messages: MessageManager; public nsfw: boolean; - public threads: ThreadManager; + public threads: ThreadManager; public topic: string | null; public createInvite(options?: CreateInviteOptions): Promise; public createWebhook(name: string, options?: ChannelWebhookCreateOptions): Promise; @@ -1412,6 +1412,7 @@ export class MessageSelectMenu extends BaseMessageComponent { } export class NewsChannel extends BaseGuildTextChannel { + public threads: ThreadManager; public type: 'GUILD_NEWS'; public addFollower(channel: GuildChannelResolvable, reason?: string): Promise; } @@ -1782,6 +1783,7 @@ export class TeamMember extends Base { export class TextChannel extends BaseGuildTextChannel { public rateLimitPerUser: number; + public threads: ThreadManager; public type: 'GUILD_TEXT'; public setRateLimitPerUser(rateLimitPerUser: number, reason?: string): Promise; } @@ -1796,6 +1798,7 @@ export class ThreadChannel extends TextBasedChannel(Channel) { public guild: Guild; public guildId: Snowflake; public readonly guildMembers: Collection; + public invitable: boolean | null; public readonly joinable: boolean; public readonly joined: boolean; public locked: boolean | null; @@ -1825,6 +1828,7 @@ export class ThreadChannel extends TextBasedChannel(Channel) { autoArchiveDuration: ThreadAutoArchiveDuration, reason?: string, ): Promise; + public setInvitable(invitable?: boolean, reason?: string): Promise; public setLocked(locked?: boolean, reason?: string): Promise; public setName(name: string, reason?: string): Promise; public setRateLimitPerUser(rateLimitPerUser: number, reason?: string): Promise; @@ -4593,6 +4597,7 @@ export type ThreadChannelTypes = 'GUILD_NEWS_THREAD' | 'GUILD_PUBLIC_THREAD' | ' export interface ThreadCreateOptions extends StartThreadOptions { startMessage?: MessageResolvable; type?: AllowedThreadType; + invitable?: AllowedThreadType extends 'GUILD_PRIVATE_THREAD' | 12 ? boolean : never; } export interface ThreadEditData { @@ -4601,6 +4606,7 @@ export interface ThreadEditData { autoArchiveDuration?: ThreadAutoArchiveDuration; rateLimitPerUser?: number; locked?: boolean; + invitable?: boolean; } export type ThreadMemberFlagsString = '';