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;