diff --git a/src/errors/Messages.js b/src/errors/Messages.js
index e631bb3ab..f99dd2d97 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 654c86c21..f3935849c 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 4d09d4609..02e82d0fb 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 6006d1049..24a3638af 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 = '';