Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: backport guild forum support to v13 #8651

Merged
merged 31 commits into from
Jan 2, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
febc042
feat: backport guild forum support to v13
aiko-chan-ai Sep 21, 2022
8f479cb
Update package-lock.json
aiko-chan-ai Sep 21, 2022
757924e
fix typescript test
aiko-chan-ai Sep 21, 2022
97090be
backport
aiko-chan-ai Sep 21, 2022
91e5e5f
fix example v14
aiko-chan-ai Sep 21, 2022
1b3ffa2
backport (2)
aiko-chan-ai Sep 22, 2022
f863ff7
Backport (3)
aiko-chan-ai Sep 22, 2022
8eb8900
Update src/managers/GuildChannelManager.js
aiko-chan-ai Sep 22, 2022
aa0b69e
Update typings/index.d.ts
aiko-chan-ai Sep 22, 2022
d319ae6
backport 4
aiko-chan-ai Sep 25, 2022
0adf9fa
Update src/structures/ForumChannel.js
aiko-chan-ai Sep 25, 2022
3f41082
Backport #8633
aiko-chan-ai Oct 3, 2022
8f61b50
Update GuildChannelManager.js
aiko-chan-ai Oct 3, 2022
54267f5
Don't change `package.json`
aiko-chan-ai Oct 10, 2022
a494ff0
Merge branch 'v13' of https://github.com/aiko-chan-ai/discord.js into…
aiko-chan-ai Oct 10, 2022
16a6c6e
Update src/structures/ForumChannel.js
aiko-chan-ai Oct 11, 2022
b597908
Update src/managers/GuildForumThreadManager.js
aiko-chan-ai Oct 11, 2022
516add1
Backport
aiko-chan-ai Oct 26, 2022
78833a7
Update ThreadChannel.js
aiko-chan-ai Oct 26, 2022
7d81bf2
eslint
aiko-chan-ai Oct 27, 2022
eb610a6
Merge branch 'v13' into v13
aiko-chan-ai Oct 31, 2022
bf119bd
Merge branch 'discordjs:v13' into v13
aiko-chan-ai Nov 4, 2022
1ffff8b
Merge branch 'discordjs:v13' into v13
aiko-chan-ai Nov 26, 2022
0c515b8
Update GuildForumThreadManager.js
aiko-chan-ai Dec 16, 2022
64f1c9c
update
aiko-chan-ai Dec 16, 2022
e440a94
#8895
aiko-chan-ai Dec 24, 2022
4fd0b41
Update CategoryChannel.js
aiko-chan-ai Dec 25, 2022
67f3fae
deprecated 'MAX'
aiko-chan-ai Dec 26, 2022
d484bc5
Update Message.js
aiko-chan-ai Dec 26, 2022
b87f524
bruh
aiko-chan-ai Dec 26, 2022
9eb4389
Merge branch 'discordjs:v13' into v13
aiko-chan-ai Dec 27, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/client/actions/WebhooksUpdate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
2 changes: 2 additions & 0 deletions src/errors/Messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ const Messages = {
NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`,

SWEEP_FILTER_RETURN: 'The return value of the sweepFilter function was not false or a Function',

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);
33 changes: 29 additions & 4 deletions src/managers/GuildChannelManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
*/

/**
Expand Down Expand Up @@ -138,13 +140,21 @@ class GuildChannelManager extends CachedManager {
position,
rateLimitPerUser,
rtcRegion,
videoQualityMode,
availableTags,
defaultReactionEmoji,
defaultSortOrder,
reason,
} = {},
) {
parent &&= this.client.channels.resolveId(parent);
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(
Expand All @@ -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,
});
Expand All @@ -175,7 +189,7 @@ class GuildChannelManager extends CachedManager {

/**
* Creates a webhook for the channel.
* @param {TextChannel|NewsChannel|VoiceChannel|Snowflake} channel The channel to create the webhook for
* @param {GuildChannelResolvable} channel The channel to create the webhook for
* @param {string} name The name of the webhook
* @param {ChannelWebhookCreateOptions} [options] Options for creating the webhook
* @returns {Promise<Webhook>} Returns the created Webhook
Expand Down Expand Up @@ -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
*/

/**
Expand Down Expand Up @@ -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,
});
Expand Down
92 changes: 92 additions & 0 deletions src/managers/GuildForumThreadManager.js
Original file line number Diff line number Diff line change
@@ -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
aiko-chan-ai marked this conversation as resolved.
Show resolved Hide resolved
* @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({
aiko-chan-ai marked this conversation as resolved.
Show resolved Hide resolved
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;
98 changes: 98 additions & 0 deletions src/managers/GuildTextThreadManager.js
Original file line number Diff line number Diff line change
@@ -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;
80 changes: 0 additions & 80 deletions src/managers/ThreadManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down