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: allow channels from uncached guilds to be returned from fetch #6034

Merged
merged 2 commits into from
Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/errors/Messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down
19 changes: 13 additions & 6 deletions src/managers/ChannelManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -74,25 +74,32 @@ 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. <warn>Many of the properties and methods on the returned channel will throw errors</warn>
*/

/**
* 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<?Channel>}
* @example
* // Fetch a channel by its id
* client.channels.fetch('222109930545610754')
* .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);
}
}

Expand Down
11 changes: 9 additions & 2 deletions src/managers/GuildChannelManager.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down Expand Up @@ -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;
}
}

Expand Down
22 changes: 11 additions & 11 deletions src/structures/Channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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;
Expand Down
15 changes: 13 additions & 2 deletions src/structures/GuildChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,23 @@ 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
* @type {Guild}
*/
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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions src/structures/StoreChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/structures/TextChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
15 changes: 13 additions & 2 deletions src/structures/ThreadChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,23 @@ 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
* @type {Guild}
*/
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}
Expand All @@ -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
Expand Down
16 changes: 11 additions & 5 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Permissions>;
private rolePermissions(role: Role): Readonly<Permissions>;

public readonly calculatedPosition: number;
public readonly deletable: boolean;
public guild: Guild;
public guildId: Snowflake;
public readonly manageable: boolean;
public readonly members: Collection<Snowflake, GuildMember>;
public name: string;
Expand Down Expand Up @@ -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';
}
Expand Down Expand Up @@ -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;
Expand All @@ -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<Snowflake, GuildMember>;
public readonly joinable: boolean;
public readonly joined: boolean;
Expand Down Expand Up @@ -2266,7 +2268,7 @@ export class BaseGuildEmojiManager extends CachedManager<Snowflake, GuildEmoji,

export class ChannelManager extends CachedManager<Snowflake, Channel, ChannelResolvable> {
public constructor(client: Client, iterable: Iterable<unknown>);
public fetch(id: Snowflake, options?: BaseFetchOptions): Promise<Channel | null>;
public fetch(id: Snowflake, options?: FetchChannelOptions): Promise<Channel | null>;
}

export class GuildApplicationCommandManager extends ApplicationCommandManager<ApplicationCommand, {}, Guild> {
Expand Down Expand Up @@ -3158,6 +3160,10 @@ export interface FetchBansOptions {
cache: boolean;
}

export interface FetchChannelOptions extends BaseFetchOptions {
allowUnknownGuild?: boolean;
}

export interface FetchedThreads {
threads: Collection<Snowflake, ThreadChannel>;
hasMore?: boolean;
Expand Down