diff --git a/src/client/websocket/packets/handlers/Ready.js b/src/client/websocket/packets/handlers/Ready.js index 4fc5363cf8ac..b1a833d5fad6 100644 --- a/src/client/websocket/packets/handlers/Ready.js +++ b/src/client/websocket/packets/handlers/Ready.js @@ -1,6 +1,6 @@ const AbstractHandler = require('./AbstractHandler'); const { Events } = require('../../../../util/Constants'); -const ClientUser = require('../../../../structures/ClientUser'); +let ClientUser; class ReadyHandler extends AbstractHandler { handle(packet) { @@ -12,6 +12,7 @@ class ReadyHandler extends AbstractHandler { data.user.user_settings = data.user_settings; data.user.user_guild_settings = data.user_guild_settings; + if (!ClientUser) ClientUser = require('../../../../structures/ClientUser'); const clientUser = new ClientUser(client, data.user); client.user = clientUser; client.readyAt = new Date(); diff --git a/src/index.js b/src/index.js index de6a6e66c7d8..244c6a5ea9a3 100644 --- a/src/index.js +++ b/src/index.js @@ -18,6 +18,7 @@ module.exports = { Permissions: require('./util/Permissions'), Snowflake: require('./util/Snowflake'), SnowflakeUtil: require('./util/Snowflake'), + Structures: require('./util/Structures'), Util: Util, util: Util, version: require('../package.json').version, @@ -46,7 +47,10 @@ module.exports = { CategoryChannel: require('./structures/CategoryChannel'), Channel: require('./structures/Channel'), ClientApplication: require('./structures/ClientApplication'), - ClientUser: require('./structures/ClientUser'), + get ClientUser() { + // This is a getter so that it properly extends any custom User class + return require('./structures/ClientUser'); + }, ClientUserChannelOverride: require('./structures/ClientUserChannelOverride'), ClientUserGuildSettings: require('./structures/ClientUserGuildSettings'), ClientUserSettings: require('./structures/ClientUserSettings'), diff --git a/src/stores/DataStore.js b/src/stores/DataStore.js index 398910d50dff..6f3e74146ed1 100644 --- a/src/stores/DataStore.js +++ b/src/stores/DataStore.js @@ -1,4 +1,5 @@ const Collection = require('../util/Collection'); +let Structures; /** * Manages the creation, retrieval and deletion of a specific data model. @@ -7,8 +8,9 @@ const Collection = require('../util/Collection'); class DataStore extends Collection { constructor(client, iterable, holds) { super(); + if (!Structures) Structures = require('../util/Structures'); Object.defineProperty(this, 'client', { value: client }); - Object.defineProperty(this, 'holds', { value: holds }); + Object.defineProperty(this, 'holds', { value: Structures.get(holds.name) }); if (iterable) for (const item of iterable) this.create(item); } diff --git a/src/structures/Channel.js b/src/structures/Channel.js index 33b851f09591..04867b118ad9 100644 --- a/src/structures/Channel.js +++ b/src/structures/Channel.js @@ -66,32 +66,37 @@ class Channel extends Base { } static create(client, data, guild) { - const DMChannel = require('./DMChannel'); - const GroupDMChannel = require('./GroupDMChannel'); - const TextChannel = require('./TextChannel'); - const VoiceChannel = require('./VoiceChannel'); - const CategoryChannel = require('./CategoryChannel'); - const GuildChannel = require('./GuildChannel'); + const Structures = require('../util/Structures'); let channel; if (data.type === ChannelTypes.DM) { + const DMChannel = Structures.get('DMChannel'); channel = new DMChannel(client, data); } else if (data.type === ChannelTypes.GROUP) { + const GroupDMChannel = Structures.get('GroupDMChannel'); channel = new GroupDMChannel(client, data); } else { guild = guild || client.guilds.get(data.guild_id); if (guild) { switch (data.type) { - case ChannelTypes.TEXT: + case ChannelTypes.TEXT: { + const TextChannel = Structures.get('TextChannel'); channel = new TextChannel(guild, data); break; - case ChannelTypes.VOICE: + } + case ChannelTypes.VOICE: { + const VoiceChannel = Structures.get('VoiceChannel'); channel = new VoiceChannel(guild, data); break; - case ChannelTypes.CATEGORY: + } + case ChannelTypes.CATEGORY: { + const CategoryChannel = Structures.get('CategoryChannel'); channel = new CategoryChannel(guild, data); break; - default: + } + default: { + const GuildChannel = Structures.get('GuildChannel'); channel = new GuildChannel(guild, data); + } } guild.channels.set(channel.id, channel); } diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index ef64339241b4..b0b3452d4575 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -1,4 +1,4 @@ -const User = require('./User'); +const Structures = require('../util/Structures'); const Collection = require('../util/Collection'); const ClientUserSettings = require('./ClientUserSettings'); const ClientUserGuildSettings = require('./ClientUserGuildSettings'); @@ -11,7 +11,7 @@ const Guild = require('./Guild'); * Represents the logged in client's Discord user. * @extends {User} */ -class ClientUser extends User { +class ClientUser extends Structures.get('User') { _patch(data) { super._patch(data); diff --git a/src/util/Structures.js b/src/util/Structures.js new file mode 100644 index 000000000000..a1cb7e1563d9 --- /dev/null +++ b/src/util/Structures.js @@ -0,0 +1,80 @@ +/** + * Allows for the extension of built-in Discord.js structures that are instantiated by {@link DataStore DataStores}. + */ +class Structures { + constructor() { + throw new Error(`The ${this.constructor.name} class may not be instantiated.`); + } + + /** + * Retrieves a structure class. + * @param {string} structure Name of the structure to retrieve + * @returns {Function} + */ + static get(structure) { + if (typeof structure === 'string') return structures[structure]; + throw new TypeError(`"structure" argument must be a string (received ${typeof structure})`); + } + + /** + * Extends a structure. + * @param {string} structure Name of the structure class to extend + * @param {Function} extender Function that takes the base class to extend as its only parameter and returns the + * extended class/prototype + * @returns {Function} Extended class/prototype returned from the extender + * @example + * const { Structures } = require('discord.js'); + * + * Structures.extend('Guild', Guild => { + * class CoolGuild extends Guild { + * constructor(client, data) { + * super(client, data); + * this.cool = true; + * } + * } + * + * return CoolGuild; + * }); + */ + static extend(structure, extender) { + if (!structures[structure]) throw new RangeError(`"${structure}" is not a valid extensible structure.`); + if (typeof extender !== 'function') { + const received = `(received ${typeof extender})`; + throw new TypeError( + `"extender" argument must be a function that returns the extended structure class/prototype ${received}` + ); + } + + const extended = extender(structures[structure]); + if (typeof extended !== 'function') { + throw new TypeError('The extender function must return the extended structure class/prototype.'); + } + if (Object.getPrototypeOf(extended) !== structures[structure]) { + throw new Error( + 'The class/prototype returned from the extender function must extend the existing structure class/prototype.' + ); + } + + structures[structure] = extended; + return extended; + } +} + +const structures = { + Emoji: require('../structures/Emoji'), + DMChannel: require('../structures/DMChannel'), + GroupDMChannel: require('../structures/GroupDMChannel'), + TextChannel: require('../structures/TextChannel'), + VoiceChannel: require('../structures/VoiceChannel'), + CategoryChannel: require('../structures/CategoryChannel'), + GuildChannel: require('../structures/GuildChannel'), + GuildMember: require('../structures/GuildMember'), + Guild: require('../structures/Guild'), + Message: require('../structures/Message'), + MessageReaction: require('../structures/MessageReaction'), + Presence: require('../structures/Presence').Presence, + Role: require('../structures/Role'), + User: require('../structures/User'), +}; + +module.exports = Structures;