Merge pull request #334 from aiko-chan-ai/forum-feature

feat: Guild Forum channel
This commit is contained in:
Cinnamon 2022-10-03 19:26:12 +07:00 committed by GitHub
commit 062ac5de89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 821 additions and 120 deletions

View File

@ -62,7 +62,7 @@
"axios": "^0.27.2",
"bignumber.js": "^9.1.0",
"chalk": "^4.1.2",
"discord-api-types": "^0.37.10",
"discord-api-types": "^0.37.11",
"form-data": "^4.0.0",
"json-bigint": "^1.0.0",
"node-fetch": "^2.6.1",

View File

@ -10,7 +10,7 @@ class WebhooksUpdate extends Action {
/**
* Emitted whenever a channel has its webhooks changed.
* @event Client#webhookUpdate
* @param {TextChannel|NewsChannel|VoiceChannel} channel The channel that had a webhook update
* @param {TextChannel|NewsChannel|VoiceChannel|ForumChannel} channel The channel that had a webhook update
*/
if (channel) client.emit(Events.WEBHOOKS_UPDATE, channel);
}

View File

@ -207,6 +207,8 @@ const Messages = {
NITRO_REQUIRED: 'This feature is only available for Nitro users.',
NITRO_BOOST_REQUIRED: feature => `This feature (${feature}) is only available for Nitro Boost users.`,
ONLY_ME: 'This feature is only available for self.',
GUILD_FORUM_MESSAGE_REQUIRED: 'You must provide a message to create a guild forum thread',
};
for (const [name, message] of Object.entries(Messages)) register(name, message);

View File

@ -9,10 +9,11 @@ const GuildChannel = require('../structures/GuildChannel');
const PermissionOverwrites = require('../structures/PermissionOverwrites');
const ThreadChannel = require('../structures/ThreadChannel');
const Webhook = require('../structures/Webhook');
const { ThreadChannelTypes, ChannelTypes, VideoQualityModes } = require('../util/Constants');
const ChannelFlags = require('../util/ChannelFlags');
const { ThreadChannelTypes, ChannelTypes, VideoQualityModes, SortOrderTypes } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const Util = require('../util/Util');
const { resolveAutoArchiveMaxLimit } = require('../util/Util');
const { resolveAutoArchiveMaxLimit, transformGuildForumTag, transformGuildDefaultReaction } = require('../util/Util');
let cacheWarningEmitted = false;
let storeChannelDeprecationEmitted = false;
@ -73,8 +74,9 @@ class GuildChannelManager extends CachedManager {
* Data that can be resolved to give a Guild Channel object. This can be:
* * A GuildChannel object
* * A ThreadChannel object
* * A ForumChannel object
* * A Snowflake
* @typedef {GuildChannel|ThreadChannel|Snowflake} GuildChannelResolvable
* @typedef {GuildChannel|ThreadChannel|ForumChannel|Snowflake} GuildChannelResolvable
*/
/**
@ -138,6 +140,10 @@ class GuildChannelManager extends CachedManager {
position,
rateLimitPerUser,
rtcRegion,
videoQualityMode,
availableTags,
defaultReactionEmoji,
defaultSortOrder,
reason,
} = {},
) {
@ -145,6 +151,10 @@ class GuildChannelManager extends CachedManager {
permissionOverwrites &&= permissionOverwrites.map(o => PermissionOverwrites.resolve(o, this.guild));
const intType = typeof type === 'number' ? type : ChannelTypes[type] ?? ChannelTypes.GUILD_TEXT;
const videoMode = typeof videoQualityMode === 'number' ? videoQualityMode : VideoQualityModes[videoQualityMode];
const sortMode = typeof defaultSortOrder === 'number' ? defaultSortOrder : SortOrderTypes[defaultSortOrder];
if (intType === ChannelTypes.GUILD_STORE && !storeChannelDeprecationEmitted) {
storeChannelDeprecationEmitted = true;
process.emitWarning(
@ -167,6 +177,10 @@ class GuildChannelManager extends CachedManager {
permission_overwrites: permissionOverwrites,
rate_limit_per_user: rateLimitPerUser,
rtc_region: rtcRegion,
video_quality_mode: videoMode,
available_tags: availableTags?.map(availableTag => transformGuildForumTag(availableTag)),
default_reaction_emoji: defaultReactionEmoji && transformGuildDefaultReaction(defaultReactionEmoji),
default_sort_order: sortMode,
},
reason,
});
@ -224,6 +238,11 @@ class GuildChannelManager extends CachedManager {
* The default auto archive duration for all new threads in this channel
* @property {?string} [rtcRegion] The RTC region of the channel
* @property {?VideoQualityMode|number} [videoQualityMode] The camera video quality mode of the channel
* @property {ChannelFlagsResolvable} [flags] The flags to set on the channel
* @property {GuildForumTagData[]} [availableTags] The tags to set as available in a forum channel
* @property {?DefaultReactionEmoji} [defaultReactionEmoji] The emoji to set as the default reaction emoji
* @property {number} [defaultThreadRateLimitPerUser] The rate limit per user (slowmode) to set on forum posts
* @property {?SortOrderType} [defaultSortOrder] The default sort order mode to set on the channel
*/
/**
@ -282,6 +301,12 @@ class GuildChannelManager extends CachedManager {
rate_limit_per_user: data.rateLimitPerUser,
default_auto_archive_duration: defaultAutoArchiveDuration,
permission_overwrites,
available_tags: data.availableTags?.map(availableTag => transformGuildForumTag(availableTag)),
default_reaction_emoji: data.defaultReactionEmoji && transformGuildDefaultReaction(data.defaultReactionEmoji),
default_thread_rate_limit_per_user: data.defaultThreadRateLimitPerUser,
flags: 'flags' in data ? ChannelFlags.resolve(data.flags) : undefined,
default_sort_order:
typeof data.defaultSortOrder === 'string' ? SortOrderTypes[data.defaultSortOrder] : data.defaultSortOrder,
},
reason,
});

View File

@ -0,0 +1,92 @@
'use strict';
const ThreadManager = require('./ThreadManager');
const { TypeError } = require('../errors');
const MessagePayload = require('../structures/MessagePayload');
const { resolveAutoArchiveMaxLimit } = require('../util/Util');
/**
* Manages API methods for threads in forum channels and stores their cache.
* @extends {ThreadManager}
*/
class GuildForumThreadManager extends ThreadManager {
/**
* The channel this Manager belongs to
* @name GuildForumThreadManager#channel
* @type {ForumChannel}
*/
/**
* @typedef {BaseMessageOptions} GuildForumThreadMessageCreateOptions
* @property {stickers} [stickers] The stickers to send with the message
* @property {BitFieldResolvable} [flags] The flags to send with the message
*/
/**
* Options for creating a thread.
* @typedef {StartThreadOptions} GuildForumThreadCreateOptions
* @property {GuildForumThreadMessageCreateOptions|MessagePayload} message The message associated with the thread post
* @property {Snowflake[]} [appliedTags] The tags to apply to the thread
*/
/**
* Creates a new thread in the channel.
* @param {GuildForumThreadCreateOptions} [options] Options to create a new thread
* @returns {Promise<ThreadChannel>}
* @example
* // Create a new forum post
* forum.threads
* .create({
* name: 'Food Talk',
* autoArchiveDuration: 60,
* message: {
* content: 'Discuss your favorite food!',
* },
* reason: 'Needed a separate thread for food',
* })
* .then(threadChannel => console.log(threadChannel))
* .catch(console.error);
*/
async create({
name,
autoArchiveDuration = this.channel.defaultAutoArchiveDuration,
message,
reason,
rateLimitPerUser,
appliedTags,
} = {}) {
let path = this.client.api.channels(this.channel.id);
if (!message) {
throw new TypeError('GUILD_FORUM_MESSAGE_REQUIRED');
}
let messagePayload;
if (message instanceof MessagePayload) {
messagePayload = message.resolveData();
} else {
messagePayload = MessagePayload.create(this, message).resolveData();
}
const { data: body, files } = await messagePayload.resolveFiles();
if (autoArchiveDuration === 'MAX') autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild);
const data = await path.threads.post({
data: {
name,
auto_archive_duration: autoArchiveDuration,
rate_limit_per_user: rateLimitPerUser,
applied_tags: appliedTags,
message: body,
},
files,
reason,
});
return this.client.actions.ThreadCreate.handle(data).thread;
}
}
module.exports = GuildForumThreadManager;

View File

@ -0,0 +1,98 @@
'use strict';
const ThreadManager = require('./ThreadManager');
const { TypeError } = require('../errors');
const { ChannelTypes } = require('../util/Constants');
const { resolveAutoArchiveMaxLimit } = require('../util/Util');
/**
* Manages API methods for {@link ThreadChannel} objects and stores their cache.
* @extends {ThreadManager}
*/
class GuildTextThreadManager extends ThreadManager {
/**
* The channel this Manager belongs to
* @name GuildTextThreadManager#channel
* @type {TextChannel|NewsChannel}
*/
/**
* Options for creating a thread. <warn>Only one of `startMessage` or `type` can be defined.</warn>
* @typedef {StartThreadOptions} GuildTextThreadCreateOptions
* @property {MessageResolvable} [startMessage] The message to start a thread from. <warn>If this is defined then type
* of thread gets automatically defined and cannot be changed. The provided `type` field will be ignored</warn>
* @property {ThreadChannelTypes|number} [type] The type of thread to create. Defaults to `GUILD_PUBLIC_THREAD` if
* created in a {@link TextChannel} <warn>When creating threads in a {@link NewsChannel} this is ignored and is always
* `GUILD_NEWS_THREAD`</warn>
* @property {boolean} [invitable] Whether non-moderators can add other non-moderators to the thread
* <info>Can only be set when type will be `GUILD_PRIVATE_THREAD`</info>
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the new channel in seconds
*/
/**
* Creates a new thread in the channel.
* @param {GuildTextThreadCreateOptions} [options] Options to create a new thread
* @returns {Promise<ThreadChannel>}
* @example
* // Create a new public thread
* channel.threads
* .create({
* name: 'food-talk',
* autoArchiveDuration: 60,
* reason: 'Needed a separate thread for food',
* })
* .then(threadChannel => console.log(threadChannel))
* .catch(console.error);
* @example
* // Create a new private thread
* channel.threads
* .create({
* name: 'mod-talk',
* autoArchiveDuration: 60,
* type: 'GUILD_PRIVATE_THREAD',
* reason: 'Needed a separate thread for moderation',
* })
* .then(threadChannel => console.log(threadChannel))
* .catch(console.error);
*/
async create({
name,
autoArchiveDuration = this.channel.defaultAutoArchiveDuration,
startMessage,
type,
invitable,
reason,
rateLimitPerUser,
} = {}) {
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');
}
let resolvedType =
this.channel.type === 'GUILD_NEWS' ? ChannelTypes.GUILD_NEWS_THREAD : ChannelTypes.GUILD_PUBLIC_THREAD;
if (startMessage) {
const startMessageId = this.channel.messages.resolveId(startMessage);
if (!startMessageId) throw new TypeError('INVALID_TYPE', 'startMessage', 'MessageResolvable');
path = path.messages(startMessageId);
} else if (this.channel.type !== 'GUILD_NEWS') {
resolvedType = typeof type === 'string' ? ChannelTypes[type] : type ?? resolvedType;
}
if (autoArchiveDuration === 'MAX') autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild);
const data = await path.threads.post({
data: {
name,
auto_archive_duration: autoArchiveDuration,
type: resolvedType,
invitable: resolvedType === ChannelTypes.GUILD_PRIVATE_THREAD ? invitable : undefined,
rate_limit_per_user: rateLimitPerUser,
},
reason,
});
return this.client.actions.ThreadCreate.handle(data).thread;
}
}
module.exports = GuildTextThreadManager;

View File

@ -4,8 +4,6 @@ const { Collection } = require('@discordjs/collection');
const CachedManager = require('./CachedManager');
const { TypeError } = require('../errors');
const ThreadChannel = require('../structures/ThreadChannel');
const { ChannelTypes } = require('../util/Constants');
const { resolveAutoArchiveMaxLimit } = require('../util/Util');
/**
* Manages API methods for {@link ThreadChannel} objects and stores their cache.
@ -60,84 +58,6 @@ class ThreadManager extends CachedManager {
* @returns {?Snowflake}
*/
/**
* Options for creating a thread. <warn>Only one of `startMessage` or `type` can be defined.</warn>
* @typedef {StartThreadOptions} ThreadCreateOptions
* @property {MessageResolvable} [startMessage] The message to start a thread from. <warn>If this is defined then type
* of thread gets automatically defined and cannot be changed. The provided `type` field will be ignored</warn>
* @property {ThreadChannelTypes|number} [type] The type of thread to create. Defaults to `GUILD_PUBLIC_THREAD` if
* created in a {@link TextChannel} <warn>When creating threads in a {@link NewsChannel} this is ignored and is always
* `GUILD_NEWS_THREAD`</warn>
* @property {boolean} [invitable] Whether non-moderators can add other non-moderators to the thread
* <info>Can only be set when type will be `GUILD_PRIVATE_THREAD`</info>
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the new channel in seconds
*/
/**
* Creates a new thread in the channel.
* @param {ThreadCreateOptions} [options] Options to create a new thread
* @returns {Promise<ThreadChannel>}
* @example
* // Create a new public thread
* channel.threads
* .create({
* name: 'food-talk',
* autoArchiveDuration: 60,
* reason: 'Needed a separate thread for food',
* })
* .then(threadChannel => console.log(threadChannel))
* .catch(console.error);
* @example
* // Create a new private thread
* channel.threads
* .create({
* name: 'mod-talk',
* autoArchiveDuration: 60,
* type: 'GUILD_PRIVATE_THREAD',
* reason: 'Needed a separate thread for moderation',
* })
* .then(threadChannel => console.log(threadChannel))
* .catch(console.error);
*/
async create({
name,
autoArchiveDuration = this.channel.defaultAutoArchiveDuration,
startMessage,
type,
invitable,
reason,
rateLimitPerUser,
} = {}) {
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');
}
let resolvedType =
this.channel.type === 'GUILD_NEWS' ? ChannelTypes.GUILD_NEWS_THREAD : ChannelTypes.GUILD_PUBLIC_THREAD;
if (startMessage) {
const startMessageId = this.channel.messages.resolveId(startMessage);
if (!startMessageId) throw new TypeError('INVALID_TYPE', 'startMessage', 'MessageResolvable');
path = path.messages(startMessageId);
} else if (this.channel.type !== 'GUILD_NEWS') {
resolvedType = typeof type === 'string' ? ChannelTypes[type] : type ?? resolvedType;
}
if (autoArchiveDuration === 'MAX') autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild);
const data = await path.threads.post({
data: {
name,
auto_archive_duration: autoArchiveDuration,
type: resolvedType,
invitable: resolvedType === ChannelTypes.GUILD_PRIVATE_THREAD ? invitable : undefined,
rate_limit_per_user: rateLimitPerUser,
},
reason,
});
return this.client.actions.ThreadCreate.handle(data).thread;
}
/**
* The options for fetching multiple threads, the properties are mutually exclusive
* @typedef {Object} FetchThreadsOptions

View File

@ -2,8 +2,8 @@
const GuildChannel = require('./GuildChannel');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const GuildTextThreadManager = require('../managers/GuildTextThreadManager');
const MessageManager = require('../managers/MessageManager');
const ThreadManager = require('../managers/ThreadManager');
/**
* Represents a text-based guild channel on Discord.
@ -22,9 +22,9 @@ class BaseGuildTextChannel extends GuildChannel {
/**
* A manager of the threads belonging to this channel
* @type {ThreadManager}
* @type {GuildTextThreadManager}
*/
this.threads = new ThreadManager(this);
this.threads = new GuildTextThreadManager(this);
/**
* If the guild considers this channel NSFW

View File

@ -40,6 +40,11 @@ class CategoryChannel extends GuildChannel {
* @property {number} [position] Position of the new channel
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the new channel in seconds
* @property {string} [rtcRegion] The specific region of the new channel.
* @property {VideoQualityMode} [videoQualityMode] The camera video quality mode of the voice channel
* @property {GuildForumTagData[]} [availableTags] The tags that can be used in this channel (forum only).
* @property {DefaultReactionEmoji} [defaultReactionEmoji]
* The emoji to show in the add reaction button on a thread in a guild forum channel.
* @property {SortOrderType} [defaultSortOrder] The default sort order mode used to order posts (forum only).
* @property {string} [reason] Reason for creating the new channel
*/

View File

@ -11,6 +11,8 @@ let TextChannel;
let ThreadChannel;
let VoiceChannel;
let DirectoryChannel;
let ForumChannel;
const ChannelFlags = require('../util/ChannelFlags');
const { ChannelTypes, ThreadChannelTypes, VoiceBasedChannelTypes } = require('../util/Constants');
const SnowflakeUtil = require('../util/SnowflakeUtil');
// Const { ApplicationCommand } = require('discord.js-selfbot-v13'); - Not being used in this file, not necessary.
@ -48,6 +50,17 @@ class Channel extends Base {
* @type {Snowflake}
*/
this.id = data.id;
if ('flags' in data) {
/**
* The flags that are applied to the channel.
* <info>This is only `null` in a {@link PartialGroupDMChannel}. In all other cases, it is not `null`.</info>
* @type {?Readonly<ChannelFlags>}
*/
this.flags = new ChannelFlags(data.flags).freeze();
} else {
this.flags ??= new ChannelFlags().freeze();
}
}
/**
@ -184,6 +197,7 @@ class Channel extends Base {
ThreadChannel ??= require('./ThreadChannel');
VoiceChannel ??= require('./VoiceChannel');
DirectoryChannel ??= require('./DirectoryChannel');
ForumChannel ??= require('./ForumChannel');
let channel;
if (!data.guild_id && !guild) {
@ -232,6 +246,9 @@ class Channel extends Base {
case ChannelTypes.GUILD_DIRECTORY:
channel = new DirectoryChannel(client, data);
break;
case ChannelTypes.GUILD_FORUM:
channel = new ForumChannel(guild, data, client);
break;
}
if (channel && !allowUnknownGuild) guild.channels?.cache.set(channel.id, channel);
}

View File

@ -0,0 +1,249 @@
'use strict';
const GuildChannel = require('./GuildChannel');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const GuildForumThreadManager = require('../managers/GuildForumThreadManager');
const { SortOrderTypes } = require('../util/Constants');
const { transformAPIGuildForumTag, transformAPIGuildDefaultReaction } = require('../util/Util');
/**
* @typedef {Object} GuildForumTagEmoji
* @property {?Snowflake} id The id of a guild's custom emoji
* @property {?string} name The unicode character of the emoji
*/
/**
* @typedef {Object} GuildForumTag
* @property {Snowflake} id The id of the tag
* @property {string} name The name of the tag
* @property {boolean} moderated Whether this tag can only be added to or removed from threads
* by a member with the `ManageThreads` permission
* @property {?GuildForumTagEmoji} emoji The emoji of this tag
*/
/**
* @typedef {Object} GuildForumTagData
* @property {Snowflake} [id] The id of the tag
* @property {string} name The name of the tag
* @property {boolean} [moderated] Whether this tag can only be added to or removed from threads
* by a member with the `ManageThreads` permission
* @property {?GuildForumTagEmoji} [emoji] The emoji of this tag
*/
/**
* @typedef {Object} DefaultReactionEmoji
* @property {?Snowflake} id The id of a guild's custom emoji
* @property {?string} name The unicode character of the emoji
*/
/**
* Represents a channel that only contains threads
* @extends {GuildChannel}
* @implements {TextBasedChannel}
*/
class ForumChannel extends GuildChannel {
constructor(guild, data, client) {
super(guild, data, client, false);
/**
* A manager of the threads belonging to this channel
* @type {GuildForumThreadManager}
*/
this.threads = new GuildForumThreadManager(this);
this._patch(data);
}
_patch(data) {
super._patch(data);
if ('available_tags' in data) {
/**
* The set of tags that can be used in this channel.
* @type {GuildForumTag[]}
*/
this.availableTags = data.available_tags.map(tag => transformAPIGuildForumTag(tag));
} else {
this.availableTags ??= [];
}
if ('default_reaction_emoji' in data) {
/**
* The emoji to show in the add reaction button on a thread in a guild forum channel
* @type {?DefaultReactionEmoji}
*/
this.defaultReactionEmoji = data.default_reaction_emoji
? transformAPIGuildDefaultReaction(data.default_reaction_emoji)
: null;
} else {
this.defaultReactionEmoji ??= null;
}
if ('default_thread_rate_limit_per_user' in data) {
/**
* The initial rate limit per user (slowmode) to set on newly created threads in a channel.
* @type {?number}
*/
this.defaultThreadRateLimitPerUser = data.default_thread_rate_limit_per_user;
} else {
this.defaultThreadRateLimitPerUser ??= null;
}
if ('rate_limit_per_user' in data) {
/**
* The rate limit per user (slowmode) for this channel.
* @type {?number}
*/
this.rateLimitPerUser = data.rate_limit_per_user;
} else {
this.rateLimitPerUser ??= null;
}
if ('default_auto_archive_duration' in data) {
/**
* The default auto archive duration for newly created threads in this channel.
* @type {?ThreadAutoArchiveDuration}
*/
this.defaultAutoArchiveDuration = data.default_auto_archive_duration;
} else {
this.defaultAutoArchiveDuration ??= null;
}
if ('nsfw' in data) {
/**
* If this channel is considered NSFW.
* @type {boolean}
*/
this.nsfw = data.nsfw;
} else {
this.nsfw ??= false;
}
if ('topic' in data) {
/**
* The topic of this channel.
* @type {?string}
*/
this.topic = data.topic;
}
if ('default_sort_order' in data) {
/**
* The default sort order mode used to order posts
* @type {?SortOrderType}
*/
this.defaultSortOrder = SortOrderTypes[data.default_sort_order];
} else {
this.defaultSortOrder ??= null;
}
}
/**
* Sets the available tags for this forum channel
* @param {GuildForumTagData[]} availableTags The tags to set as available in this channel
* @param {string} [reason] Reason for changing the available tags
* @returns {Promise<ForumChannel>}
*/
setAvailableTags(availableTags, reason) {
return this.edit({ availableTags, reason });
}
/**
* Sets the default reaction emoji for this channel
* @param {?DefaultReactionEmoji} defaultReactionEmoji The emoji to set as the default reaction emoji
* @param {string} [reason] Reason for changing the default reaction emoji
* @returns {Promise<ForumChannel>}
*/
setDefaultReactionEmoji(defaultReactionEmoji, reason) {
return this.edit({ defaultReactionEmoji, reason });
}
/**
* Sets the default rate limit per user (slowmode) for new threads in this channel
* @param {number} defaultThreadRateLimitPerUser The rate limit to set on newly created threads in this channel
* @param {string} [reason] Reason for changing the default rate limit
* @returns {Promise<ForumChannel>}
*/
setDefaultThreadRateLimitPerUser(defaultThreadRateLimitPerUser, reason) {
return this.edit({ defaultThreadRateLimitPerUser, reason });
}
/**
* Sets the default sort order mode used to order posts
* @param {?SortOrderType} defaultSortOrder The default sort order mode to set on this channel
* @param {string} [reason] Reason for changing the default sort order
* @returns {Promise<ForumChannel>}
*/
setDefaultSortOrder(defaultSortOrder, reason) {
return this.edit({ defaultSortOrder, reason });
}
/**
* Creates an invite to this guild channel.
* @param {CreateInviteOptions} [options={}] The options for creating the invite
* @returns {Promise<Invite>}
* @example
* // Create an invite to a channel
* channel.createInvite()
* .then(invite => console.log(`Created an invite with a code of ${invite.code}`))
* .catch(console.error);
*/
createInvite(options) {
return this.guild.invites.create(this.id, options);
}
/**
* Fetches a collection of invites to this guild channel.
* Resolves with a collection mapping invites by their codes.
* @param {boolean} [cache=true] Whether or not to cache the fetched invites
* @returns {Promise<Collection<string, Invite>>}
*/
fetchInvites(cache = true) {
return this.guild.invites.fetch({ channelId: this.id, cache });
}
/**
* Sets the default auto archive duration for all newly created threads in this channel.
* @param {ThreadAutoArchiveDuration} defaultAutoArchiveDuration The new default auto archive duration
* @param {string} [reason] Reason for changing the channel's default auto archive duration
* @returns {Promise<ForumChannel>}
*/
setDefaultAutoArchiveDuration(defaultAutoArchiveDuration, reason) {
return this.edit({ defaultAutoArchiveDuration, reason });
}
/**
* Sets a new topic for the guild channel.
* @param {?string} topic The new topic for the guild channel
* @param {string} [reason] Reason for changing the guild channel's topic
* @returns {Promise<ForumChannel>}
* @example
* // Set a new channel topic
* channel.setTopic('needs more rate limiting')
* .then(newChannel => console.log(`Channel's new topic is ${newChannel.topic}`))
* .catch(console.error);
*/
setTopic(topic, reason) {
return this.edit({ topic, reason });
}
// These are here only for documentation purposes - they are implemented by TextBasedChannel
/* eslint-disable no-empty-function */
createWebhook() {}
fetchWebhooks() {}
setNSFW() {}
setRateLimitPerUser() {}
}
TextBasedChannel.applyToClass(ForumChannel, true, [
'send',
'lastMessage',
'lastPinAt',
'bulkDelete',
'sendTyping',
'createMessageCollector',
'awaitMessages',
'createMessageComponentCollector',
'awaitMessageComponent',
]);
module.exports = ForumChannel;

View File

@ -564,7 +564,7 @@ class Guild extends AnonymousGuild {
/**
* Widget channel for this guild
* @type {?TextChannel}
* @type {?(TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel)}
* @readonly
*/
get widgetChannel() {

View File

@ -14,6 +14,7 @@ const Permissions = require('../util/Permissions');
* - {@link NewsChannel}
* - {@link StoreChannel}
* - {@link StageChannel}
* - {@link ForumChannel}
* @extends {Channel}
* @abstract
*/

View File

@ -61,6 +61,17 @@ class Message extends Base {
*/
this.id = data.id;
if ('position' in data) {
/**
* A generally increasing integer (there may be gaps or duplicates) that represents
* the approximate position of the message in a thread.
* @type {?number}
*/
this.position = data.position;
} else {
this.position ??= null;
}
/**
* The timestamp the message was sent at
* @type {number}
@ -860,7 +871,7 @@ class Message extends Base {
/**
* Create a new public thread from this message
* @see ThreadManager#create
* @see GuildTextThreadManager#create
* @param {StartThreadOptions} [options] Options for starting a thread on this message
* @returns {Promise<ThreadChannel>}
*/

View File

@ -144,9 +144,11 @@ class MessagePayload {
let username;
let avatarURL;
let threadName;
if (isWebhook) {
username = this.options.username ?? this.target.name;
if (this.options.avatarURL) avatarURL = this.options.avatarURL;
if (this.options.threadName) threadName = this.options.threadName;
}
let flags;
@ -259,6 +261,7 @@ class MessagePayload {
message_reference,
attachments: this.options.attachments,
sticker_ids: this.options.stickers?.map(sticker => sticker.id ?? sticker),
thread_name: threadName,
};
return this;
}

View File

@ -17,6 +17,10 @@ const DataResolver = require('../util/DataResolver');
class PartialGroupDMChannel extends Channel {
constructor(client, data) {
super(client, data);
// No flags are present when fetching partial group DM channels.
this.flags = null;
/**
* The name of this Group DM Channel
* @type {?string}

View File

@ -158,9 +158,8 @@ class ThreadChannel extends Channel {
if ('message_count' in data) {
/**
* The approximate count of messages in this thread
* <info>This stops counting at 50. If you need an approximate value higher than that, use
* `ThreadChannel#messages.cache.size`</info>
* <info>Threads created before July 1, 2022 may have an inaccurate count.
* If you need an approximate value higher than that, use `ThreadChannel#messages.cache.size`</info>
* @type {?number}
*/
this.messageCount = data.message_count;
@ -180,6 +179,27 @@ class ThreadChannel extends Channel {
this.memberCount ??= null;
}
if ('total_message_sent' in data) {
/**
* The number of messages ever sent in a thread, similar to {@link ThreadChannel#messageCount} except it
* will not decrement whenever a message is deleted
* @type {?number}
*/
this.totalMessageSent = data.total_message_sent;
} else {
this.totalMessageSent ??= null;
}
if ('applied_tags' in data) {
/**
* The tags applied to this thread
* @type {Snowflake[]}
*/
this.appliedTags = data.applied_tags;
} else {
this.appliedTags ??= [];
}
if (data.member && this.client.user) this.members._add({ user_id: this.client.user.id, ...data.member });
if (data.messages) for (const message of data.messages) this.messages._add(message);
}
@ -225,7 +245,7 @@ class ThreadChannel extends Channel {
/**
* The parent channel of this thread
* @type {?(NewsChannel|TextChannel)}
* @type {?(NewsChannel|TextChannel|ForumChannel)}
* @readonly
*/
get parent() {
@ -280,14 +300,16 @@ class ThreadChannel extends Channel {
/**
* Fetches the message that started this thread, if any.
* <info>This only works when the thread started from a message in the parent channel, otherwise the promise will
* reject. If you just need the id of that message, use {@link ThreadChannel#id} instead.</info>
* <info>The `Promise` will reject if the original message in a forum post is deleted
* or when the original message in the parent channel is deleted.
* If you just need the id of that message, use {@link ThreadChannel#id} instead.</info>
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<Message|null>}
*/
// eslint-disable-next-line require-await
async fetchStarterMessage(options) {
return this.parent?.messages.fetch(this.id, options) ?? null;
const channel = this.parent?.type === 'GUILD_FORUM' ? this : this.parent;
return channel?.messages.fetch({ message: this.id, ...options }) ?? null;
}
/**
@ -326,6 +348,7 @@ class ThreadChannel extends Channel {
rate_limit_per_user: data.rateLimitPerUser,
locked: data.locked,
invitable: this.type === 'GUILD_PRIVATE_THREAD' ? data.invitable : undefined,
applied_tags: data.appliedTags,
},
reason,
});
@ -333,6 +356,16 @@ class ThreadChannel extends Channel {
return this.client.actions.ChannelUpdate.handle(newData).updated;
}
/**
* Set the applied tags for this channel (only applicable to forum threads)
* @param {Snowflake[]} appliedTags The tags to set for this channel
* @param {string} [reason] Reason for changing the thread's applied tags
* @returns {Promise<GuildForumThreadChannel>}
*/
setAppliedTags(appliedTags, reason) {
return this.edit({ appliedTags, reason });
}
/**
* Sets whether the thread is archived.
* @param {boolean} [archived=true] Whether the thread is archived

View File

@ -132,6 +132,7 @@ class Webhook {
* Action rows containing interactive components for the message (buttons, select menus)
* @property {Snowflake} [threadId] The id of the thread this message belongs to
* <info>For interaction webhooks, this property is ignored</info>
* @property {string} [threadName] Name of the thread to create (only available if webhook is in a forum channel)
*/
/**

View File

@ -42,7 +42,7 @@ class WelcomeChannel extends Base {
/**
* The channel of this welcome channel
* @type {?(TextChannel|NewsChannel|StoreChannel)}
* @type {?(TextChannel|NewsChannel|StoreChannel|ForumChannel)}
*/
get channel() {
return this.client.channels.resolve(this.channelId);

45
src/util/ChannelFlags.js Normal file
View File

@ -0,0 +1,45 @@
'use strict';
const BitField = require('./BitField');
/**
* Data structure that makes it easy to interact with a {@link BaseChannel#flags} bitfield.
* @extends {BitField}
*/
class ChannelFlags extends BitField {}
/**
* Numeric guild channel flags. All available properties:
* * `PINNED`
* * `REQUIRE_TAG`
* @type {Object}
* @see {@link https://discord.com/developers/docs/resources/channel#channel-object-channel-flags}
*/
ChannelFlags.FLAGS = {
PINNED: 1 << 1,
REQUIRE_TAG: 1 << 4,
};
/**
* @name ChannelFlags
* @kind constructor
* @memberof ChannelFlags
* @param {BitFieldResolvable} [bits=0] Bit(s) to read from
*/
/**
* Bitfield of the packed bits
* @type {number}
* @name ChannelFlags#bitfield
*/
/**
* Data that can be resolved to give a channel flag bitfield. This can be:
* * A string (see {@link ChannelFlags.FLAGS})
* * A channel flag
* * An instance of ChannelFlags
* * An Array of ChannelFlags
* @typedef {string|number|ChannelFlags|ChannelFlagsResolvable[]} ChannelFlagsResolvable
*/
module.exports = ChannelFlags;

View File

@ -1640,6 +1640,15 @@ exports.GuildScheduledEventEntityTypes = createEnum([null, 'STAGE_INSTANCE', 'VO
*/
exports.VideoQualityModes = createEnum([null, 'AUTO', 'FULL']);
/**
* Sort {@link ForumChannel} posts by ?
* * LATEST_ACTIVITY
* * CREATION_DATE
* @typedef {string} SortOrderType
* @see {@link https://discord.com/developers/docs/resources/channel/#channel-object-sort-order-types}
*/
exports.SortOrderTypes = createEnum([null, 'LATEST_ACTIVITY', 'CREATION_DATE']);
exports._cleanupSymbol = Symbol('djsCleanup');
function keyMirror(arr) {

View File

@ -606,6 +606,71 @@ class Util extends null {
let defaultValue;
return () => (defaultValue ??= cb());
}
/**
* Transforms an API guild forum tag to camel-cased guild forum tag.
* @param {APIGuildForumTag} tag The tag to transform
* @returns {GuildForumTag}
* @ignore
*/
static transformAPIGuildForumTag(tag) {
return {
id: tag.id,
name: tag.name,
moderated: tag.moderated,
emoji:
tag.emoji_id ?? tag.emoji_name
? {
id: tag.emoji_id,
name: tag.emoji_name,
}
: null,
};
}
/**
* Transforms a camel-cased guild forum tag to an API guild forum tag.
* @param {GuildForumTag} tag The tag to transform
* @returns {APIGuildForumTag}
* @ignore
*/
static transformGuildForumTag(tag) {
return {
id: tag.id,
name: tag.name,
moderated: tag.moderated,
emoji_id: tag.emoji?.id ?? null,
emoji_name: tag.emoji?.name ?? null,
};
}
/**
* Transforms an API guild forum default reaction object to a
* camel-cased guild forum default reaction object.
* @param {APIGuildForumDefaultReactionEmoji} defaultReaction The default reaction to transform
* @returns {DefaultReactionEmoji}
* @ignore
*/
static transformAPIGuildDefaultReaction(defaultReaction) {
return {
id: defaultReaction.emoji_id,
name: defaultReaction.emoji_name,
};
}
/**
* Transforms a camel-cased guild forum default reaction object to an
* API guild forum default reaction object.
* @param {DefaultReactionEmoji} defaultReaction The default reaction to transform
* @returns {APIGuildForumDefaultReactionEmoji}
* @ignore
*/
static transformGuildDefaultReaction(defaultReaction) {
return {
emoji_id: defaultReaction.id,
emoji_name: defaultReaction.name,
};
}
}
module.exports = Util;

165
typings/index.d.ts vendored
View File

@ -53,6 +53,7 @@ import {
RESTPostAPIApplicationCommandsJSONBody,
Snowflake,
LocalizationMap,
SortOrderType,
} from 'discord-api-types/v9';
import { ChildProcess } from 'node:child_process';
import { EventEmitter } from 'node:events';
@ -674,7 +675,7 @@ export class BaseGuildTextChannel extends TextBasedChannelMixin(GuildChannel) {
public defaultAutoArchiveDuration?: ThreadAutoArchiveDuration;
public rateLimitPerUser: number | null;
public nsfw: boolean;
public threads: ThreadManager<AllowedThreadTypeForTextChannel | AllowedThreadTypeForNewsChannel>;
public threads: GuildTextThreadManager<AllowedThreadTypeForTextChannel | AllowedThreadTypeForNewsChannel>;
public topic: string | null;
public createInvite(options?: CreateInviteOptions): Promise<Invite>;
public fetchInvites(cache?: boolean): Promise<Collection<string, Invite>>;
@ -757,7 +758,7 @@ export type MappedChannelCategoryTypes = EnumValueMapped<
GUILD_TEXT: TextChannel;
GUILD_STORE: StoreChannel;
GUILD_STAGE_VOICE: StageChannel;
GUILD_FORUM: undefined;
GUILD_FORUM: ForumChannel;
}
>;
@ -801,6 +802,7 @@ export abstract class Channel extends Base {
public id: Snowflake;
public readonly partial: false;
public type: keyof typeof ChannelTypes;
public flags: Readonly<ChannelFlags> | null;
public delete(): Promise<this>;
public fetch(force?: boolean): Promise<this>;
public isText(): this is TextBasedChannel;
@ -1193,6 +1195,7 @@ export class DMChannel extends TextBasedChannelMixin(Channel, [
private constructor(client: Client, data?: RawDMChannelData);
public recipient: User;
public type: 'DM';
public flags: Readonly<ChannelFlags>;
public fetch(force?: boolean): Promise<this>;
public readonly voiceAdapterCreator: InternalDiscordGatewayAdapterCreator;
public call(options?: object): Promise<VoiceConnection>;
@ -1272,7 +1275,7 @@ export class Guild extends AnonymousGuild {
public vanityURLUses: number | null;
public readonly voiceAdapterCreator: InternalDiscordGatewayAdapterCreator;
public readonly voiceStates: VoiceStateManager;
public readonly widgetChannel: TextChannel | null;
public readonly widgetChannel: TextChannel | NewsChannel | VoiceBasedChannel | ForumChannel | null;
public widgetChannelId: Snowflake | null;
public widgetEnabled: boolean | null;
public readonly maximumBitrate: number;
@ -1416,6 +1419,7 @@ export abstract class GuildChannel extends Channel {
public readonly position: number;
public rawPosition: number;
public type: Exclude<keyof typeof ChannelTypes, 'DM' | 'GROUP_DM' | 'UNKNOWN'>;
public flags: Readonly<ChannelFlags>;
public readonly viewable: boolean;
public clone(options?: GuildChannelCloneOptions): Promise<this>;
public delete(reason?: string): Promise<this>;
@ -1900,6 +1904,7 @@ export class Message<Cached extends boolean = boolean> extends Base {
public webhookId: Snowflake | null;
public flags: Readonly<MessageFlags>;
public reference: MessageReference | null;
public position: number | null;
public awaitMessageComponent<T extends MessageComponentTypeResolvable = 'ACTION_ROW'>(
options?: AwaitMessageCollectorOptionsParams<T, Cached>,
): Promise<MappedInteractionTypes<Cached>[T]>;
@ -2334,7 +2339,7 @@ export class ModalSubmitInteraction<Cached extends CacheType = CacheType> extend
}
export class NewsChannel extends BaseGuildTextChannel {
public threads: ThreadManager<AllowedThreadTypeForNewsChannel>;
public threads: GuildTextThreadManager<AllowedThreadTypeForNewsChannel>;
public type: 'GUILD_NEWS';
public addFollower(channel: TextChannelResolvable, reason?: string): Promise<NewsChannel>;
}
@ -2362,6 +2367,7 @@ export class PartialGroupDMChannel extends TextBasedChannelMixin(Channel, [
public lastPinTimestamp: number | null;
public owner: User | null;
public ownerId: Snowflake | null;
public flags: null;
public iconURL(options?: StaticImageURLOptions): string | null;
public addMember(user: User): Promise<PartialGroupDMChannel>;
public removeMember(user: User): Promise<PartialGroupDMChannel>;
@ -2661,7 +2667,9 @@ export class StageChannel extends BaseGuildVoiceChannel {
public setTopic(topic: string): Promise<StageChannel>;
}
export class DirectoryChannel extends Channel {}
export class DirectoryChannel extends Channel {
public flags: Readonly<ChannelFlags>;
}
export class StageInstance extends Base {
private constructor(client: Client, data: RawStageInstanceData, channel: StageChannel);
@ -2835,7 +2843,7 @@ export class TeamMember extends Base {
export class TextChannel extends BaseGuildTextChannel {
public rateLimitPerUser: number;
public threads: ThreadManager<AllowedThreadTypeForTextChannel>;
public threads: GuildTextThreadManager<AllowedThreadTypeForTextChannel>;
public type: 'GUILD_TEXT';
}
@ -2886,10 +2894,13 @@ export class ThreadChannel extends TextBasedChannelMixin(Channel, ['fetchWebhook
public members: ThreadMemberManager;
public name: string;
public ownerId: Snowflake | null;
public readonly parent: TextChannel | NewsChannel | null;
public readonly parent: TextChannel | NewsChannel | ForumChannel | null;
public parentId: Snowflake | null;
public rateLimitPerUser: number | null;
public type: ThreadChannelTypes;
public flags: Readonly<ChannelFlags>;
public appliedTags: Snowflake[];
public totalMessageSent: number | null;
public readonly unarchivable: boolean;
public isPrivate(): this is this & {
readonly createdTimestamp: number;
@ -2915,6 +2926,7 @@ export class ThreadChannel extends TextBasedChannelMixin(Channel, ['fetchWebhook
public setInvitable(invitable?: boolean, reason?: string): Promise<ThreadChannel>;
public setLocked(locked?: boolean, reason?: string): Promise<ThreadChannel>;
public setName(name: string, reason?: string): Promise<ThreadChannel>;
public setAppliedTags(appliedTags: Snowflake[], reason?: string): Promise<ThreadChannel>;
}
export class ThreadMember extends Base {
@ -3296,7 +3308,7 @@ export class WelcomeChannel extends Base {
public channelId: Snowflake;
public guild: Guild | InviteGuild;
public description: string;
public readonly channel: TextChannel | NewsChannel | StoreChannel | null;
public readonly channel: TextChannel | NewsChannel | StoreChannel | ForumChannel | null;
public readonly emoji: GuildEmoji | Emoji;
}
@ -3979,10 +3991,9 @@ export class StageInstanceManager extends CachedManager<Snowflake, StageInstance
public delete(channel: StageChannelResolvable): Promise<void>;
}
export class ThreadManager<AllowedThreadType> extends CachedManager<Snowflake, ThreadChannel, ThreadChannelResolvable> {
private constructor(channel: TextChannel | NewsChannel, iterable?: Iterable<RawThreadChannelData>);
export class ThreadManager extends CachedManager<Snowflake, ThreadChannel, ThreadChannelResolvable> {
protected constructor(channel: TextChannel | NewsChannel, iterable?: Iterable<RawThreadChannelData>);
public channel: TextChannel | NewsChannel;
public create(options: ThreadCreateOptions<AllowedThreadType>): Promise<ThreadChannel>;
public fetch(options: ThreadChannelResolvable, cacheOptions?: BaseFetchOptions): Promise<ThreadChannel | null>;
public fetch(options?: FetchThreadsOptions, cacheOptions?: { cache?: boolean }): Promise<FetchedThreads>;
public fetchArchived(options?: FetchArchivedThreadOptions, cache?: boolean): Promise<FetchedThreads>;
@ -4413,7 +4424,7 @@ export interface ClientEvents extends BaseClientEvents {
userUpdate: [oldUser: User | PartialUser, newUser: User];
userSettingsUpdate: [setting: RawUserSettingsData];
voiceStateUpdate: [oldState: VoiceState, newState: VoiceState];
webhookUpdate: [channel: TextChannel | NewsChannel | VoiceChannel];
webhookUpdate: [channel: TextChannel | NewsChannel | VoiceChannel | ForumChannel];
/** @deprecated Use interactionCreate instead */
interaction: [interaction: Interaction];
interactionCreate: [interaction: Interaction | { nonce: Snowflake; id: Snowflake }];
@ -4969,6 +4980,10 @@ export interface CategoryCreateChannelOptions {
rateLimitPerUser?: number;
position?: number;
rtcRegion?: string;
videoQualityMode?: VideoQualityMode;
availableTags?: GuildForumTagData[];
defaultReactionEmoji?: DefaultReactionEmoji;
defaultSortOrder?: SortOrderType;
reason?: string;
}
@ -4993,6 +5008,11 @@ export interface ChannelData {
defaultAutoArchiveDuration?: ThreadAutoArchiveDuration | 'MAX';
rtcRegion?: string | null;
videoQualityMode?: VideoQualityMode | null;
availableTags?: GuildForumTagData[];
defaultReactionEmoji?: DefaultReactionEmoji;
defaultThreadRateLimitPerUser?: number;
defaultSortOrder?: SortOrderType | null;
flags?: ChannelFlagsResolvable;
}
export interface ChannelLogsQueryOptions {
@ -6015,11 +6035,32 @@ export type MessageComponentTypeResolvable = MessageComponentType | MessageCompo
export interface MessageEditOptions {
attachments?: MessageAttachment[];
content?: string | null;
embeds?: (MessageEmbed | MessageEmbedOptions | APIEmbed)[] | null;
files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[];
flags?: BitFieldResolvable<MessageFlagsString, number>;
allowedMentions?: MessageMentionOptions;
components?: (MessageActionRow | (Required<BaseMessageComponentOptions> & MessageActionRowOptions))[];
id?: Snowflake | number;
parentId?: Snowflake | number;
type?: ExcludeEnum<
typeof ChannelTypes,
| 'DM'
| 'GROUP_DM'
| 'GUILD_NEWS'
| 'GUILD_STORE'
| 'UNKNOWN'
| 'GUILD_NEWS_THREAD'
| 'GUILD_PUBLIC_THREAD'
| 'GUILD_PRIVATE_THREAD'
| 'GUILD_STAGE_VOICE'
>;
name: string;
topic?: string;
nsfw?: boolean;
bitrate?: number;
userLimit?: number;
rtcRegion?: string | null;
videoQualityMode?: VideoQualityMode;
permissionOverwrites?: PartialOverwriteData[];
rateLimitPerUser?: number;
availableTags?: GuildForumTagData[];
defaultReactionEmoji?: DefaultReactionEmoji;
defaultThreadRateLimitPerUser?: number;
}
export interface MessageEmbedAuthor {
@ -6572,9 +6613,10 @@ export type AnyChannel =
| StoreChannel
| TextChannel
| ThreadChannel
| VoiceChannel;
| VoiceChannel
| ForumChannel;
export type TextBasedChannel = Extract<AnyChannel, { messages: MessageManager }>;
export type TextBasedChannel = Exclude<Extract<AnyChannel, { messages: MessageManager }>, ForumChannel>;
export type TextBasedChannelTypes = TextBasedChannel['type'];
@ -6603,7 +6645,7 @@ export type GuildBasedChannel = Extract<AnyChannel, { guild: Guild }>;
export type NonThreadGuildBasedChannel = Exclude<GuildBasedChannel, ThreadChannel>;
export type GuildTextBasedChannel = Extract<GuildBasedChannel, TextBasedChannel>;
export type GuildTextBasedChannel = Exclude<Extract<GuildBasedChannel, TextBasedChannel>, ForumChannel>;
export type TextChannelResolvable = Snowflake | TextChannel;
@ -6615,7 +6657,7 @@ export type ThreadChannelResolvable = ThreadChannel | Snowflake;
export type ThreadChannelTypes = 'GUILD_NEWS_THREAD' | 'GUILD_PUBLIC_THREAD' | 'GUILD_PRIVATE_THREAD';
export interface ThreadCreateOptions<AllowedThreadType> extends StartThreadOptions {
export interface GuildTextThreadCreateOptions<AllowedThreadType> extends StartThreadOptions {
startMessage?: MessageResolvable;
type?: AllowedThreadType;
invitable?: AllowedThreadType extends 'GUILD_PRIVATE_THREAD' | 12 ? boolean : never;
@ -6629,6 +6671,7 @@ export interface ThreadEditData {
rateLimitPerUser?: number;
locked?: boolean;
invitable?: boolean;
threadName?: string;
}
export type ThreadMemberFlagsString = '';
@ -6737,7 +6780,7 @@ export interface WidgetChannel {
export interface WelcomeChannelData {
description: string;
channel: TextChannel | NewsChannel | StoreChannel | Snowflake;
channel: TextChannel | NewsChannel | StoreChannel | ForumChannel | Snowflake;
emoji?: EmojiIdentifierResolvable;
}
@ -6840,4 +6883,82 @@ export type InternalDiscordGatewayAdapterCreator = (
methods: InternalDiscordGatewayAdapterLibraryMethods,
) => InternalDiscordGatewayAdapterImplementerMethods;
// GuildForum
export type ChannelFlagsString =
| 'PINNED'
| 'REQUIRE_TAG';
export class ChannelFlags extends BitField<ChannelFlagsString> {
public static FLAGS: Record<ChannelFlagsString, number>;
public static resolve(bit?: BitFieldResolvable<ChannelFlagsString, number>): number;
}
export interface GuildForumTagEmoji {
id: Snowflake | null;
name: string | null;
}
export interface GuildForumTag {
id: Snowflake;
name: string;
moderated: boolean;
emoji: GuildForumTagEmoji | null;
}
export type GuildForumTagData = Partial<GuildForumTag> & { name: string };
export interface DefaultReactionEmoji {
id: Snowflake | null;
name: string | null;
}
export class ForumChannel extends TextBasedChannelMixin(GuildChannel, [
'send',
'lastMessage',
'lastPinAt',
'bulkDelete',
'sendTyping',
'createMessageCollector',
'awaitMessages',
'createMessageComponentCollector',
'awaitMessageComponent',
]) {
public type: 'GUILD_FORUM';
public threads: GuildForumThreadManager;
public availableTags: GuildForumTag[];
public defaultReactionEmoji: DefaultReactionEmoji | null;
public defaultThreadRateLimitPerUser: number | null;
public rateLimitPerUser: number | null;
public defaultAutoArchiveDuration: ThreadAutoArchiveDuration | null;
public nsfw: boolean;
public topic: string | null;
public setAvailableTags(tags: GuildForumTagData[], reason?: string): Promise<this>;
public setDefaultReactionEmoji(emojiId: DefaultReactionEmoji | null, reason?: string): Promise<this>;
public setDefaultThreadRateLimitPerUser(rateLimit: number, reason?: string): Promise<this>;
public createInvite(options?: CreateInviteOptions): Promise<Invite>;
public fetchInvites(cache?: boolean): Promise<Collection<string, Invite>>;
public setDefaultAutoArchiveDuration(
defaultAutoArchiveDuration: ThreadAutoArchiveDuration,
reason?: string,
): Promise<this>;
public setTopic(topic: string | null, reason?: string): Promise<this>;
public setDefaultSortOrder(defaultSortOrder: SortOrderType | null, reason?: string): Promise<this>;
}
export class GuildTextThreadManager<AllowedThreadType> extends ThreadManager {
public create(options: GuildTextThreadCreateOptions<AllowedThreadType>): Promise<ThreadChannel>;
}
export class GuildForumThreadManager extends ThreadManager {
public create(options: GuildForumThreadCreateOptions): Promise<ThreadChannel>;
}
export type GuildForumThreadMessageCreateOptions = MessageOptions & Pick<MessageOptions, 'flags' | 'stickers'>;
export type ChannelFlagsResolvable = BitFieldResolvable<ChannelFlagsString, number>;
export interface GuildForumThreadCreateOptions extends StartThreadOptions {
message: GuildForumThreadMessageCreateOptions | MessagePayload;
appliedTags?: Snowflake[];
}
//#endregion