diff --git a/src/managers/ChannelManager.js b/src/managers/ChannelManager.js index 4de961bc248b..357c5af22539 100644 --- a/src/managers/ChannelManager.js +++ b/src/managers/ChannelManager.js @@ -19,10 +19,10 @@ class ChannelManager extends CachedManager { * @name ChannelManager#cache */ - _add(data, guild, cache = true, allowUnknownGuild = false) { + _add(data, guild, { cache = true, allowUnknownGuild = false, fromInteraction = false } = {}) { const existing = this.cache.get(data.id); if (existing) { - if (cache) existing._patch(data); + if (cache) existing._patch(data, fromInteraction); guild?.channels?._add(existing); if (ThreadChannelTypes.includes(existing.type)) { existing.parent?.threads?._add(existing); @@ -30,7 +30,7 @@ class ChannelManager extends CachedManager { return existing; } - const channel = Channel.create(this.client, data, guild, allowUnknownGuild); + const channel = Channel.create(this.client, data, guild, { allowUnknownGuild, fromInteraction }); if (!channel) { this.client.emit(Events.DEBUG, `Failed to find guild, or unknown type for channel ${data.id} ${data.type}`); @@ -99,7 +99,7 @@ class ChannelManager extends CachedManager { } const data = await this.client.api.channels(id).get(); - return this._add(data, null, cache, allowUnknownGuild); + return this._add(data, null, { cache, allowUnknownGuild }); } } diff --git a/src/managers/GuildChannelManager.js b/src/managers/GuildChannelManager.js index 46fdd6cdaaee..075315c48557 100644 --- a/src/managers/GuildChannelManager.js +++ b/src/managers/GuildChannelManager.js @@ -169,12 +169,12 @@ class GuildChannelManager extends CachedManager { const data = await this.client.api.channels(id).get(); // Since this is the guild manager, throw if on a different guild if (this.guild.id !== data.guild_id) throw new Error('GUILD_CHANNEL_UNOWNED'); - return this.client.channels._add(data, this.guild, cache); + return this.client.channels._add(data, this.guild, { cache }); } const data = await this.client.api.guilds(this.guild.id).channels.get(); const channels = new Collection(); - for (const channel of data) channels.set(channel.id, this.client.channels._add(channel, this.guild, cache)); + for (const channel of data) channels.set(channel.id, this.client.channels._add(channel, this.guild, { cache })); return channels; } } diff --git a/src/managers/ThreadManager.js b/src/managers/ThreadManager.js index 4853791cfa73..43141f1bd420 100644 --- a/src/managers/ThreadManager.js +++ b/src/managers/ThreadManager.js @@ -235,7 +235,7 @@ class ThreadManager extends CachedManager { _mapThreads(rawThreads, cache) { const threads = rawThreads.threads.reduce((coll, raw) => { - const thread = this.client.channels._add(raw, null, cache); + const thread = this.client.channels._add(raw, null, { cache }); return coll.set(thread.id, thread); }, new Collection()); // Discord sends the thread id as id in this object diff --git a/src/structures/Channel.js b/src/structures/Channel.js index 6dbcaa93214a..596892345f9e 100644 --- a/src/structures/Channel.js +++ b/src/structures/Channel.js @@ -126,7 +126,7 @@ class Channel extends Base { return ThreadChannelTypes.includes(this.type); } - static create(client, data, guild, allowUnknownGuild) { + static create(client, data, guild, { allowUnknownGuild, fromInteraction }) { if (!CategoryChannel) CategoryChannel = require('./CategoryChannel'); if (!DMChannel) DMChannel = require('./DMChannel'); if (!NewsChannel) NewsChannel = require('./NewsChannel'); @@ -176,7 +176,7 @@ class Channel extends Base { case ChannelTypes.GUILD_NEWS_THREAD: case ChannelTypes.GUILD_PUBLIC_THREAD: case ChannelTypes.GUILD_PRIVATE_THREAD: { - channel = new ThreadChannel(guild, data, client); + channel = new ThreadChannel(guild, data, client, fromInteraction); if (!allowUnknownGuild) channel.parent?.threads.cache.set(channel.id, channel); break; } diff --git a/src/structures/CommandInteraction.js b/src/structures/CommandInteraction.js index ecae1d71ce04..5bc6b1e8211f 100644 --- a/src/structures/CommandInteraction.js +++ b/src/structures/CommandInteraction.js @@ -121,7 +121,9 @@ class CommandInteraction extends Interaction { if (member) result.member = this.guild?.members._add({ user, ...member }) ?? member; const channel = resolved.channels?.[option.value]; - if (channel) result.channel = this.client.channels._add(channel, this.guild) ?? channel; + if (channel) { + result.channel = this.client.channels._add(channel, this.guild, { fromInteraction: true }) ?? channel; + } const role = resolved.roles?.[option.value]; if (role) result.role = this.guild?.roles._add(role) ?? role; diff --git a/src/structures/Invite.js b/src/structures/Invite.js index f325eceef754..95bad22b21eb 100644 --- a/src/structures/Invite.js +++ b/src/structures/Invite.js @@ -109,7 +109,7 @@ class Invite extends Base { * The channel the invite is for * @type {Channel} */ - this.channel = this.client.channels._add(data.channel, this.guild, false); + this.channel = this.client.channels._add(data.channel, this.guild, { cache: false }); /** * The timestamp the invite was created at diff --git a/src/structures/ThreadChannel.js b/src/structures/ThreadChannel.js index aad85652c23a..8c411daed902 100644 --- a/src/structures/ThreadChannel.js +++ b/src/structures/ThreadChannel.js @@ -16,8 +16,9 @@ class ThreadChannel extends Channel { * @param {Guild} guild The guild the thread channel is part of * @param {APIChannel} data The data for the thread channel * @param {Client} [client] A safety parameter for the client that instantiated this + * @param {boolean} [fromInteraction=false] Whether the data was from an interaction (partial) */ - constructor(guild, data, client) { + constructor(guild, data, client, fromInteraction = false) { super(guild?.client ?? client, data, false); /** @@ -43,10 +44,10 @@ class ThreadChannel extends Channel { * @type {ThreadMemberManager} */ this.members = new ThreadMemberManager(this); - if (data) this._patch(data); + if (data) this._patch(data, fromInteraction); } - _patch(data) { + _patch(data, partial = false) { super._patch(data); /** @@ -62,27 +63,29 @@ class ThreadChannel extends Channel { if ('parent_id' in data) { /** * The id of the parent channel of this thread - * @type {Snowflake} + * @type {?Snowflake} */ this.parentId = data.parent_id; + } else if (!this.parentId) { + this.parentId = null; } if ('thread_metadata' in data) { /** * Whether the thread is locked - * @type {boolean} + * @type {?boolean} */ this.locked = data.thread_metadata.locked ?? false; /** * Whether the thread is archived - * @type {boolean} + * @type {?boolean} */ this.archived = data.thread_metadata.archived; /** * The amount of time (in minutes) after which the thread will automatically archive in case of no recent activity - * @type {number} + * @type {?number} */ this.autoArchiveDuration = data.thread_metadata.auto_archive_duration; @@ -90,9 +93,22 @@ class ThreadChannel extends Channel { * The timestamp when the thread's archive status was last changed * If the thread was never archived or unarchived, this is the timestamp at which the thread was * created - * @type {number} + * @type {?number} */ this.archiveTimestamp = new Date(data.thread_metadata.archive_timestamp).getTime(); + } else { + if (!this.locked) { + this.locked = null; + } + if (!this.archived) { + this.archived = null; + } + if (!this.autoArchiveDuration) { + this.autoArchiveDuration = null; + } + if (!this.archiveTimestamp) { + this.archiveTimestamp = null; + } } if ('owner_id' in data) { @@ -101,6 +117,8 @@ class ThreadChannel extends Channel { * @type {?Snowflake} */ this.ownerId = data.owner_id; + } else if (!this.ownerId) { + this.ownerId = null; } if ('last_message_id' in data) { @@ -109,6 +127,8 @@ class ThreadChannel extends Channel { * @type {?Snowflake} */ this.lastMessageId = data.last_message_id; + } else if (!this.lastMessageId) { + this.lastMessageId = null; } if ('last_pin_timestamp' in data) { @@ -117,14 +137,18 @@ class ThreadChannel extends Channel { * @type {?number} */ this.lastPinTimestamp = data.last_pin_timestamp ? new Date(data.last_pin_timestamp).getTime() : null; + } else if (!this.lastPinTimestamp) { + this.lastPinTimestamp = null; } - if ('rate_limit_per_user' in data) { + if ('rate_limit_per_user' in data || !partial) { /** * The ratelimit per user for this thread (in seconds) - * @type {number} + * @type {?number} */ this.rateLimitPerUser = data.rate_limit_per_user ?? 0; + } else if (!this.rateLimitPerUser) { + this.rateLimitPerUser = null; } if ('message_count' in data) { @@ -132,9 +156,11 @@ class ThreadChannel extends Channel { * The approximate count of messages in this thread * This stops counting at 50. If you need an approximate value higher than that, use * `ThreadChannel#messages.cache.size` - * @type {number} + * @type {?number} */ this.messageCount = data.message_count; + } else if (!this.messageCount) { + this.messageCount = null; } if ('member_count' in data) { @@ -142,9 +168,11 @@ class ThreadChannel extends Channel { * The approximate count of users in this thread * This stops counting at 50. If you need an approximate value higher than that, use * `ThreadChannel#members.cache.size` - * @type {number} + * @type {?number} */ this.memberCount = data.member_count; + } else if (!this.memberCount) { + this.memberCount = null; } if (data.member && this.client.user) this.members._add({ user_id: this.client.user.id, ...data.member }); @@ -163,10 +191,11 @@ class ThreadChannel extends Channel { /** * The time at which this thread's archive status was last changed * If the thread was never archived or unarchived, this is the time at which the thread was created - * @type {Date} + * @type {?Date} * @readonly */ get archivedAt() { + if (!this.archiveTimestamp) return null; return new Date(this.archiveTimestamp); } diff --git a/typings/index.d.ts b/typings/index.d.ts index 882fa2755f7a..46c7ae47a2da 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1667,18 +1667,18 @@ export class TextChannel extends TextBasedChannel(GuildChannel) { } export class ThreadChannel extends TextBasedChannel(Channel) { - public constructor(guild: Guild, data?: object, client?: Client); - public archived: boolean; - public readonly archivedAt: Date; - public archiveTimestamp: number; - public autoArchiveDuration: ThreadAutoArchiveDuration; + public constructor(guild: Guild, data?: object, client?: Client, fromInteraction?: boolean); + public archived: boolean | null; + public readonly archivedAt: Date | null; + public archiveTimestamp: number | null; + public autoArchiveDuration: ThreadAutoArchiveDuration | null; public readonly editable: boolean; public guild: Guild; public guildId: Snowflake; public readonly guildMembers: Collection; public readonly joinable: boolean; public readonly joined: boolean; - public locked: boolean; + public locked: boolean | null; public readonly manageable: boolean; public readonly sendable: boolean; public memberCount: number | null; @@ -1686,10 +1686,10 @@ export class ThreadChannel extends TextBasedChannel(Channel) { public messages: MessageManager; public members: ThreadMemberManager; public name: string; - public ownerId: Snowflake; + public ownerId: Snowflake | null; public readonly parent: TextChannel | NewsChannel | null; - public parentId: Snowflake; - public rateLimitPerUser: number; + public parentId: Snowflake | null; + public rateLimitPerUser: number | null; public type: ThreadChannelTypes; public readonly unarchivable: boolean; public delete(reason?: string): Promise;