From 755c180659c125532fe6f8e33e6c3b56e275311b Mon Sep 17 00:00:00 2001 From: ckohen Date: Tue, 6 Jul 2021 05:04:26 -0700 Subject: [PATCH] feat: allow channels from uncached guilds to be returned from fetch (#6034) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Antonio Román --- src/errors/Messages.js | 1 + src/managers/ChannelManager.js | 19 +++++++++++++------ src/managers/GuildChannelManager.js | 11 +++++++++-- src/structures/Channel.js | 22 +++++++++++----------- src/structures/GuildChannel.js | 15 +++++++++++++-- src/structures/StoreChannel.js | 9 +++++---- src/structures/TextChannel.js | 5 +++-- src/structures/ThreadChannel.js | 15 +++++++++++++-- typings/index.d.ts | 16 +++++++++++----- 9 files changed, 79 insertions(+), 34 deletions(-) diff --git a/src/errors/Messages.js b/src/errors/Messages.js index 4a10d1877..f082ceaed 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -90,6 +90,7 @@ const Messages = { GUILD_CHANNEL_RESOLVE: 'Could not resolve channel to a guild channel.', GUILD_VOICE_CHANNEL_RESOLVE: 'Could not resolve channel to a guild voice channel.', GUILD_CHANNEL_ORPHAN: 'Could not find a parent to this guild channel.', + GUILD_CHANNEL_UNOWNED: "The fetched channel does not belong to this manager's guild.", GUILD_OWNED: 'Guild is owned by the client.', GUILD_MEMBERS_TIMEOUT: "Members didn't arrive in time.", GUILD_UNCACHED_ME: 'The client user as a member of this guild is uncached.', diff --git a/src/managers/ChannelManager.js b/src/managers/ChannelManager.js index 4bba91183..e28af76c6 100644 --- a/src/managers/ChannelManager.js +++ b/src/managers/ChannelManager.js @@ -19,7 +19,7 @@ class ChannelManager extends CachedManager { * @name ChannelManager#cache */ - add(data, guild, cache = true) { + add(data, guild, cache = true, allowUnknownGuild = false) { const existing = this.cache.get(data.id); if (existing) { if (cache) existing._patch(data); @@ -30,14 +30,14 @@ class ChannelManager extends CachedManager { return existing; } - const channel = Channel.create(this.client, data, guild); + const channel = Channel.create(this.client, data, guild, allowUnknownGuild); if (!channel) { this.client.emit(Events.DEBUG, `Failed to find guild, or unknown type for channel ${data.id} ${data.type}`); return null; } - if (cache) this.cache.set(channel.id, channel); + if (cache && !allowUnknownGuild) this.cache.set(channel.id, channel); return channel; } @@ -74,10 +74,17 @@ class ChannelManager extends CachedManager { * @returns {?Snowflake} */ + /** + * Options for fetching a channel from discord + * @typedef {BaseFetchOptions} FetchChannelOptions + * @property {boolean} [allowUnknownGuild=false] Allows the channel to be returned even if the guild is not in cache, + * it will not be cached. Many of the properties and methods on the returned channel will throw errors + */ + /** * Obtains a channel from Discord, or the channel cache if it's already available. * @param {Snowflake} id The channel's id - * @param {BaseFetchOptions} [options] Additional options for this fetch + * @param {FetchChannelOptions} [options] Additional options for this fetch * @returns {Promise} * @example * // Fetch a channel by its id @@ -85,14 +92,14 @@ class ChannelManager extends CachedManager { * .then(channel => console.log(channel.name)) * .catch(console.error); */ - async fetch(id, { cache = true, force = false } = {}) { + async fetch(id, { allowUnknownGuild = false, cache = true, force = false } = {}) { if (!force) { const existing = this.cache.get(id); if (existing && !existing.partial) return existing; } const data = await this.client.api.channels(id).get(); - return this.add(data, null, cache); + return this.add(data, null, cache, allowUnknownGuild); } } diff --git a/src/managers/GuildChannelManager.js b/src/managers/GuildChannelManager.js index 5f82dcc12..62c8102fc 100644 --- a/src/managers/GuildChannelManager.js +++ b/src/managers/GuildChannelManager.js @@ -1,6 +1,7 @@ 'use strict'; const CachedManager = require('./CachedManager'); +const { Error } = require('../errors'); const GuildChannel = require('../structures/GuildChannel'); const PermissionOverwrites = require('../structures/PermissionOverwrites'); const ThreadChannel = require('../structures/ThreadChannel'); @@ -164,11 +165,17 @@ class GuildChannelManager extends CachedManager { if (existing) return existing; } - // We cannot fetch a single guild channel, as of this commit's date, Discord API throws with 404 + if (id) { + 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); + } + 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)); - return id ? channels.get(id) ?? null : channels; + return channels; } } diff --git a/src/structures/Channel.js b/src/structures/Channel.js index c992d1dad..37a7e5e73 100644 --- a/src/structures/Channel.js +++ b/src/structures/Channel.js @@ -127,7 +127,7 @@ class Channel extends Base { return ThreadChannelTypes.includes(this.type); } - static create(client, data, guild) { + static create(client, data, guild, allowUnknownGuild) { if (!CategoryChannel) CategoryChannel = require('./CategoryChannel'); if (!DMChannel) DMChannel = require('./DMChannel'); if (!NewsChannel) NewsChannel = require('./NewsChannel'); @@ -148,41 +148,41 @@ class Channel extends Base { } else { if (!guild) guild = client.guilds.cache.get(data.guild_id); - if (guild) { + if (guild || allowUnknownGuild) { switch (data.type) { case ChannelTypes.TEXT: { - channel = new TextChannel(guild, data); + channel = new TextChannel(guild, data, client); break; } case ChannelTypes.VOICE: { - channel = new VoiceChannel(guild, data); + channel = new VoiceChannel(guild, data, client); break; } case ChannelTypes.CATEGORY: { - channel = new CategoryChannel(guild, data); + channel = new CategoryChannel(guild, data, client); break; } case ChannelTypes.NEWS: { - channel = new NewsChannel(guild, data); + channel = new NewsChannel(guild, data, client); break; } case ChannelTypes.STORE: { - channel = new StoreChannel(guild, data); + channel = new StoreChannel(guild, data, client); break; } case ChannelTypes.STAGE: { - channel = new StageChannel(guild, data); + channel = new StageChannel(guild, data, client); break; } case ChannelTypes.NEWS_THREAD: case ChannelTypes.PUBLIC_THREAD: case ChannelTypes.PRIVATE_THREAD: { - channel = new ThreadChannel(guild, data); - channel.parent?.threads.cache.set(channel.id, channel); + channel = new ThreadChannel(guild, data, client); + if (!allowUnknownGuild) channel.parent?.threads.cache.set(channel.id, channel); break; } } - if (channel) guild.channels?.cache.set(channel.id, channel); + if (channel && !allowUnknownGuild) guild.channels?.cache.set(channel.id, channel); } } return channel; diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index c0e970513..598b6c6fa 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -24,9 +24,10 @@ class GuildChannel extends Channel { /** * @param {Guild} guild The guild the guild channel is part of * @param {APIChannel} data The data for the guild channel + * @param {Client} [client] A safety parameter for the client that instantiated this */ - constructor(guild, data) { - super(guild.client, data, false); + constructor(guild, data, client) { + super(guild?.client ?? client, data, false); /** * The guild the channel is in @@ -34,6 +35,12 @@ class GuildChannel extends Channel { */ this.guild = guild; + /** + * The id of the guild the channel is in + * @type {Snowflake} + */ + this.guildId = guild?.id ?? data.guild_id; + this.parentId = this.parentId ?? null; /** * A manager of permission overwrites that belong to this channel @@ -63,6 +70,10 @@ class GuildChannel extends Channel { this.rawPosition = data.position; } + if ('guild_id' in data) { + this.guildId = data.guild_id; + } + if ('parent_id' in data) { /** * The id of the category parent of this channel diff --git a/src/structures/StoreChannel.js b/src/structures/StoreChannel.js index c2bc287f1..26aea9306 100644 --- a/src/structures/StoreChannel.js +++ b/src/structures/StoreChannel.js @@ -8,11 +8,12 @@ const GuildChannel = require('./GuildChannel'); */ class StoreChannel extends GuildChannel { /** - * @param {*} guild The guild the store channel is part of - * @param {*} data The data for the store channel + * @param {Guild} guild The guild the store channel is part of + * @param {APIChannel} data The data for the store channel + * @param {Client} [client] A safety parameter for the client that instantiated this */ - constructor(guild, data) { - super(guild, data); + constructor(guild, data, client) { + super(guild, data, client); /** * If the guild considers this channel NSFW diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index 1489bca0d..490d523db 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -17,9 +17,10 @@ class TextChannel extends GuildChannel { /** * @param {Guild} guild The guild the text channel is part of * @param {APIChannel} data The data for the text channel + * @param {Client} [client] A safety parameter for the client that instantiated this */ - constructor(guild, data) { - super(guild, data); + constructor(guild, data, client) { + super(guild, data, client); /** * A manager of the messages sent to this channel * @type {MessageManager} diff --git a/src/structures/ThreadChannel.js b/src/structures/ThreadChannel.js index e0de36495..83dca99de 100644 --- a/src/structures/ThreadChannel.js +++ b/src/structures/ThreadChannel.js @@ -15,9 +15,10 @@ 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 */ - constructor(guild, data) { - super(guild.client, data, false); + constructor(guild, data, client) { + super(guild?.client ?? client, data, false); /** * The guild the thread is in @@ -25,6 +26,12 @@ class ThreadChannel extends Channel { */ this.guild = guild; + /** + * The id of the guild the channel is in + * @type {Snowflake} + */ + this.guildId = guild?.id ?? data.guild_id; + /** * A manager of the messages sent to this thread * @type {MessageManager} @@ -50,6 +57,10 @@ class ThreadChannel extends Channel { */ this.name = data.name; + if ('guild_id' in data) { + this.guildId = data.guild_id; + } + if ('parent_id' in data) { /** * The id of the parent channel of this thread diff --git a/typings/index.d.ts b/typings/index.d.ts index e4090b6b9..324baeeb6 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -619,13 +619,14 @@ export class GuildBan extends Base { } export class GuildChannel extends Channel { - public constructor(guild: Guild, data?: unknown); + public constructor(guild: Guild, data?: unknown, client?: Client); private memberPermissions(member: GuildMember): Readonly; private rolePermissions(role: Role): Readonly; public readonly calculatedPosition: number; public readonly deletable: boolean; public guild: Guild; + public guildId: Snowflake; public readonly manageable: boolean; public readonly members: Collection; public name: string; @@ -1519,7 +1520,7 @@ export class Sticker extends Base { } export class StoreChannel extends GuildChannel { - public constructor(guild: Guild, data?: unknown); + public constructor(guild: Guild, data?: unknown, client?: Client); public nsfw: boolean; public type: 'store'; } @@ -1558,7 +1559,7 @@ export class TeamMember extends Base { } export class TextChannel extends TextBasedChannel(GuildChannel) { - public constructor(guild: Guild, data?: unknown); + public constructor(guild: Guild, data?: unknown, client?: Client); public defaultAutoArchiveDuration?: ThreadAutoArchiveDuration; public messages: MessageManager; public nsfw: boolean; @@ -1578,13 +1579,14 @@ export class TextChannel extends TextBasedChannel(GuildChannel) { } export class ThreadChannel extends TextBasedChannel(Channel) { - public constructor(guild: Guild, data?: object); + public constructor(guild: Guild, data?: object, client?: Client); public archived: boolean; public readonly archivedAt: Date; public archiveTimestamp: number; public autoArchiveDuration: ThreadAutoArchiveDuration; public readonly editable: boolean; public guild: Guild; + public guildId: Snowflake; public readonly guildMembers: Collection; public readonly joinable: boolean; public readonly joined: boolean; @@ -2266,7 +2268,7 @@ export class BaseGuildEmojiManager extends CachedManager { public constructor(client: Client, iterable: Iterable); - public fetch(id: Snowflake, options?: BaseFetchOptions): Promise; + public fetch(id: Snowflake, options?: FetchChannelOptions): Promise; } export class GuildApplicationCommandManager extends ApplicationCommandManager { @@ -3158,6 +3160,10 @@ export interface FetchBansOptions { cache: boolean; } +export interface FetchChannelOptions extends BaseFetchOptions { + allowUnknownGuild?: boolean; +} + export interface FetchedThreads { threads: Collection; hasMore?: boolean;