Datastore cleanup (#1892)

* Start Store cleanup

* wip store cleanup

* fix iterables initiating datastores

* handle the possibility of a datastore with no holds and no its own 'create' method

* more cleanup (instances that need more than just client/data)

* missed RoleStore extras

* not sure how eslint didn't catch that tab...

* avoid re-getting the channel we already have...

* cleanup resolvers and refactor them into DataStores

* ^

* and remove

* fix some bugs

* fix lint

* fix documentation maybe?

* formatting

* fix presences

* really fix presences this time

* bad fix was bad... let;s see how bad this one is..

* forgot to save a file

* make presence resolving take userresolveables too

* fix tabs in jsdocs

* fix bad fix from last night that caused issues, with better fix...

* oops
This commit is contained in:
bdistin
2017-09-08 16:06:10 -05:00
committed by Crawl
parent 0607720ec8
commit dd085ceaee
34 changed files with 560 additions and 435 deletions

View File

@@ -1,6 +1,5 @@
const EventEmitter = require('events'); const EventEmitter = require('events');
const RESTManager = require('../rest/RESTManager'); const RESTManager = require('../rest/RESTManager');
const ClientDataResolver = require('./ClientDataResolver');
const Util = require('../util/Util'); const Util = require('../util/Util');
const Constants = require('../util/Constants'); const Constants = require('../util/Constants');
@@ -25,13 +24,6 @@ class BaseClient extends EventEmitter {
*/ */
this.rest = new RESTManager(this, options._tokenType); this.rest = new RESTManager(this, options._tokenType);
/**
* The data resolver of the client
* @type {ClientDataResolver}
* @private
*/
this.resolver = new ClientDataResolver(this);
/** /**
* Timeouts set by {@link WebhookClient#setTimeout} that are still active * Timeouts set by {@link WebhookClient#setTimeout} that are still active
* @type {Set<Timeout>} * @type {Set<Timeout>}

View File

@@ -17,6 +17,7 @@ const ChannelStore = require('../stores/ChannelStore');
const GuildStore = require('../stores/GuildStore'); const GuildStore = require('../stores/GuildStore');
const ClientPresenceStore = require('../stores/ClientPresenceStore'); const ClientPresenceStore = require('../stores/ClientPresenceStore');
const Constants = require('../util/Constants'); const Constants = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const { Error, TypeError, RangeError } = require('../errors'); const { Error, TypeError, RangeError } = require('../errors');
/** /**
@@ -292,7 +293,7 @@ class Client extends BaseClient {
* @returns {Promise<Invite>} * @returns {Promise<Invite>}
*/ */
fetchInvite(invite) { fetchInvite(invite) {
const code = this.resolver.resolveInviteCode(invite); const code = DataResolver.resolveInviteCode(invite);
return this.api.invites(code).get({ query: { with_counts: true } }) return this.api.invites(code).get({ query: { with_counts: true } })
.then(data => new Invite(this, data)); .then(data => new Invite(this, data));
} }

View File

@@ -1,282 +0,0 @@
const path = require('path');
const fs = require('fs');
const snekfetch = require('snekfetch');
const Util = require('../util/Util');
const User = require('../structures/User');
const Message = require('../structures/Message');
const Guild = require('../structures/Guild');
const Channel = require('../structures/Channel');
const GuildMember = require('../structures/GuildMember');
const Role = require('../structures/Role');
const Emoji = require('../structures/Emoji');
const ReactionEmoji = require('../structures/ReactionEmoji');
const { Error, TypeError } = require('../errors');
/**
* The DataResolver identifies different objects and tries to resolve a specific piece of information from them, e.g.
* extracting a User from a Message object.
* @private
*/
class ClientDataResolver {
/**
* @param {Client} client The client the resolver is for
*/
constructor(client) {
this.client = client;
}
/**
* Data that resolves to give a User object. This can be:
* * A User object
* * A Snowflake
* * A Message object (resolves to the message author)
* * A GuildMember object
* @typedef {User|Snowflake|Message|GuildMember} UserResolvable
*/
/**
* Resolves a UserResolvable to a User object.
* @param {UserResolvable} user The UserResolvable to identify
* @returns {?User}
*/
resolveUser(user) {
if (user instanceof User) return user;
if (typeof user === 'string') return this.client.users.get(user) || null;
if (user instanceof GuildMember) return user.user;
if (user instanceof Message) return user.author;
return null;
}
/**
* Resolves a UserResolvable to a user ID string.
* @param {UserResolvable} user The UserResolvable to identify
* @returns {?Snowflake}
*/
resolveUserID(user) {
if (user instanceof User || user instanceof GuildMember) return user.id;
if (typeof user === 'string') return user || null;
if (user instanceof Message) return user.author.id;
return null;
}
/**
* Data that resolves to give a Guild object. This can be:
* * A Guild object
* * A Snowflake
* @typedef {Guild|Snowflake} GuildResolvable
*/
/**
* Resolves a GuildResolvable to a Guild object.
* @param {GuildResolvable} guild The GuildResolvable to identify
* @returns {?Guild}
*/
resolveGuild(guild) {
if (guild instanceof Guild) return guild;
if (typeof guild === 'string') return this.client.guilds.get(guild) || null;
return null;
}
/**
* Data that resolves to give a GuildMember object. This can be:
* * A GuildMember object
* * A User object
* @typedef {GuildMember|User} GuildMemberResolvable
*/
/**
* Resolves a GuildMemberResolvable to a GuildMember object.
* @param {GuildResolvable} guild The guild that the member is part of
* @param {UserResolvable} user The user that is part of the guild
* @returns {?GuildMember}
*/
resolveGuildMember(guild, user) {
if (user instanceof GuildMember) return user;
guild = this.resolveGuild(guild);
user = this.resolveUser(user);
if (!guild || !user) return null;
return guild.members.get(user.id) || null;
}
/**
* Data that can be resolved to a Role object. This can be:
* * A Role
* * A Snowflake
* @typedef {Role|Snowflake} RoleResolvable
*/
/**
* Resolves a RoleResolvable to a Role object.
* @param {GuildResolvable} guild The guild that this role is part of
* @param {RoleResolvable} role The role resolvable to resolve
* @returns {?Role}
*/
resolveRole(guild, role) {
if (role instanceof Role) return role;
guild = this.resolveGuild(guild);
if (!guild) return null;
if (typeof role === 'string') return guild.roles.get(role);
return null;
}
/**
* Data that can be resolved to give a Channel object. This can be:
* * A Channel object
* * A Snowflake
* @typedef {Channel|Snowflake} ChannelResolvable
*/
/**
* Resolves a ChannelResolvable to a Channel object.
* @param {ChannelResolvable} channel The channel resolvable to resolve
* @returns {?Channel}
*/
resolveChannel(channel) {
if (channel instanceof Channel) return channel;
if (typeof channel === 'string') return this.client.channels.get(channel) || null;
return null;
}
/**
* Resolves a ChannelResolvable to a channel ID.
* @param {ChannelResolvable} channel The channel resolvable to resolve
* @returns {?Snowflake}
*/
resolveChannelID(channel) {
if (channel instanceof Channel) return channel.id;
if (typeof channel === 'string') return channel;
return null;
}
/**
* Data that can be resolved to give an invite code. This can be:
* * An invite code
* * An invite URL
* @typedef {string} InviteResolvable
*/
/**
* Resolves InviteResolvable to an invite code.
* @param {InviteResolvable} data The invite resolvable to resolve
* @returns {string}
*/
resolveInviteCode(data) {
const inviteRegex = /discord(?:app\.com\/invite|\.gg)\/([\w-]{2,255})/i;
const match = inviteRegex.exec(data);
if (match && match[1]) return match[1];
return data;
}
/**
* Resolves a Base64Resolvable, a string, or a BufferResolvable to a Base 64 image.
* @param {BufferResolvable|Base64Resolvable} image The image to be resolved
* @returns {Promise<?string>}
*/
async resolveImage(image) {
if (!image) return null;
if (typeof image === 'string' && image.startsWith('data:')) {
return image;
}
const file = await this.resolveFile(image);
return this.resolveBase64(file);
}
/**
* Data that resolves to give a Base64 string, typically for image uploading. This can be:
* * A Buffer
* * A base64 string
* @typedef {Buffer|string} Base64Resolvable
*/
/**
* Resolves a Base64Resolvable to a Base 64 image.
* @param {Base64Resolvable} data The base 64 resolvable you want to resolve
* @returns {?string}
*/
resolveBase64(data) {
if (data instanceof Buffer) return `data:image/jpg;base64,${data.toString('base64')}`;
return data;
}
/**
* Data that can be resolved to give a Buffer. This can be:
* * A Buffer
* * The path to a local file
* * A URL
* @typedef {string|Buffer} BufferResolvable
*/
/**
* @external Stream
* @see {@link https://nodejs.org/api/stream.html}
*/
/**
* Resolves a BufferResolvable to a Buffer.
* @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve
* @returns {Promise<Buffer>}
*/
resolveFile(resource) {
if (resource instanceof Buffer) return Promise.resolve(resource);
if (this.client.browser && resource instanceof ArrayBuffer) return Promise.resolve(Util.convertToBuffer(resource));
if (typeof resource === 'string') {
return new Promise((resolve, reject) => {
if (/^https?:\/\//.test(resource)) {
snekfetch.get(resource)
.end((err, res) => {
if (err) return reject(err);
if (!(res.body instanceof Buffer)) return reject(new TypeError('REQ_BODY_TYPE'));
return resolve(res.body);
});
} else {
const file = path.resolve(resource);
fs.stat(file, (err, stats) => {
if (err) return reject(err);
if (!stats || !stats.isFile()) return reject(new Error('FILE_NOT_FOUND', file));
fs.readFile(file, (err2, data) => {
if (err2) reject(err2); else resolve(data);
});
return null;
});
}
});
} else if (resource.pipe && typeof resource.pipe === 'function') {
return new Promise((resolve, reject) => {
const buffers = [];
resource.once('error', reject);
resource.on('data', data => buffers.push(data));
resource.once('end', () => resolve(Buffer.concat(buffers)));
});
}
return Promise.reject(new TypeError('REQ_RESOURCE_TYPE'));
}
/**
* Data that can be resolved to give an emoji identifier. This can be:
* * The unicode representation of an emoji
* * A custom emoji ID
* * An Emoji object
* * A ReactionEmoji object
* @typedef {string|Snowflake|Emoji|ReactionEmoji} EmojiIdentifierResolvable
*/
/**
* Resolves an EmojiResolvable to an emoji identifier.
* @param {EmojiIdentifierResolvable} emoji The emoji resolvable to resolve
* @returns {?string}
*/
resolveEmojiIdentifier(emoji) {
if (emoji instanceof Emoji || emoji instanceof ReactionEmoji) return emoji.identifier;
if (typeof emoji === 'string') {
if (this.client.emojis.has(emoji)) return this.client.emojis.get(emoji).identifier;
else if (!emoji.includes('%')) return encodeURIComponent(emoji);
else return emoji;
}
return null;
}
}
module.exports = ClientDataResolver;

View File

@@ -123,7 +123,7 @@ class VoiceReceiver extends EventEmitter {
* @returns {ReadableStream} * @returns {ReadableStream}
*/ */
createOpusStream(user) { createOpusStream(user) {
user = this.voiceConnection.voiceManager.client.resolver.resolveUser(user); user = this.voiceConnection.voiceManager.client.users.resolve(user);
if (!user) throw new Error('VOICE_USER_MISSING'); if (!user) throw new Error('VOICE_USER_MISSING');
if (this.opusStreams.get(user.id)) throw new Error('VOICE_STREAM_EXISTS'); if (this.opusStreams.get(user.id)) throw new Error('VOICE_STREAM_EXISTS');
const stream = new Readable(); const stream = new Readable();
@@ -138,7 +138,7 @@ class VoiceReceiver extends EventEmitter {
* @returns {ReadableStream} * @returns {ReadableStream}
*/ */
createPCMStream(user) { createPCMStream(user) {
user = this.voiceConnection.voiceManager.client.resolver.resolveUser(user); user = this.voiceConnection.voiceManager.client.users.resolve(user);
if (!user) throw new Error('VOICE_USER_MISSING'); if (!user) throw new Error('VOICE_USER_MISSING');
if (this.pcmStreams.get(user.id)) throw new Error('VOICE_STREAM_EXISTS'); if (this.pcmStreams.get(user.id)) throw new Error('VOICE_STREAM_EXISTS');
const stream = new Readable(); const stream = new Readable();

View File

@@ -12,6 +12,7 @@ module.exports = {
// Utilities // Utilities
Collection: require('./util/Collection'), Collection: require('./util/Collection'),
Constants: require('./util/Constants'), Constants: require('./util/Constants'),
DataResolver: require('./util/DataResolver'),
DiscordAPIError: require('./rest/DiscordAPIError'), DiscordAPIError: require('./rest/DiscordAPIError'),
EvaluatedPermissions: require('./util/Permissions'), EvaluatedPermissions: require('./util/Permissions'),
Permissions: require('./util/Permissions'), Permissions: require('./util/Permissions'),

View File

@@ -16,7 +16,7 @@ class ChannelStore extends DataStore {
options = iterableOrOptions; options = iterableOrOptions;
iterableOrOptions = undefined; iterableOrOptions = undefined;
} }
super(client, iterableOrOptions); super(client, iterableOrOptions, Channel);
if (options.lru) { if (options.lru) {
const lru = this[kLru] = []; const lru = this[kLru] = [];
@@ -72,6 +72,29 @@ class ChannelStore extends DataStore {
if (channel.guild) channel.guild.channels.remove(id); if (channel.guild) channel.guild.channels.remove(id);
super.remove(id); super.remove(id);
} }
/**
* Data that can be resolved to give a Channel object. This can be:
* * A Channel object
* * A Snowflake
* @typedef {Channel|Snowflake} ChannelResolvable
*/
/**
* Resolves a ChannelResolvable to a Channel object.
* @method resolve
* @memberof ChannelStore
* @param {ChannelResolvable} channel The channel resolvable to resolve
* @returns {?Channel}
*/
/**
* Resolves a ChannelResolvable to a channel ID string.
* @method resolveID
* @memberof ChannelStore
* @param {ChannelResolvable} channel The channel resolvable to resolve
* @returns {?string}
*/
} }
module.exports = ChannelStore; module.exports = ChannelStore;

View File

@@ -5,15 +5,45 @@ const Collection = require('../util/Collection');
* @extends {Collection} * @extends {Collection}
*/ */
class DataStore extends Collection { class DataStore extends Collection {
constructor(client, iterable) { constructor(client, iterable, holds) {
super(); super();
Object.defineProperty(this, 'client', { value: client }); Object.defineProperty(this, 'client', { value: client });
Object.defineProperty(this, 'holds', { value: holds });
if (iterable) for (const item of iterable) this.create(item); if (iterable) for (const item of iterable) this.create(item);
} }
// Stubs create(data, cache = true, { id, extras = [] } = {}) {
create() { return undefined; } const existing = this.get(id || data.id);
if (existing) return existing;
const entry = this.holds ? new this.holds(this.client, data, ...extras) : data;
if (cache) this.set(id || entry.id, entry);
return entry;
}
remove(key) { return this.delete(key); } remove(key) { return this.delete(key); }
/**
* Resolves a data entry to a data Object.
* @param {string|Object} idOrInstance The id or instance of something in this datastore
* @returns {?Object} An instance from this datastore
*/
resolve(idOrInstance) {
if (idOrInstance instanceof this.holds) return idOrInstance;
if (typeof idOrInstance === 'string') return this.get(idOrInstance) || null;
return null;
}
/**
* Resolves a data entry to a instance ID.
* @param {string|Instance} idOrInstance The id or instance of something in this datastore
* @returns {?string}
*/
resolveID(idOrInstance) {
if (idOrInstance instanceof this.holds) return idOrInstance.id;
if (typeof channel === 'string') return idOrInstance;
return null;
}
} }
module.exports = DataStore; module.exports = DataStore;

View File

@@ -1,5 +1,7 @@
const DataStore = require('./DataStore'); const DataStore = require('./DataStore');
const Emoji = require('../structures/Emoji'); const Emoji = require('../structures/Emoji');
const ReactionEmoji = require('../structures/ReactionEmoji');
/** /**
* Stores emojis. * Stores emojis.
* @private * @private
@@ -7,20 +9,62 @@ const Emoji = require('../structures/Emoji');
*/ */
class EmojiStore extends DataStore { class EmojiStore extends DataStore {
constructor(guild, iterable) { constructor(guild, iterable) {
super(guild.client, iterable); super(guild.client, iterable, Emoji);
this.guild = guild; this.guild = guild;
} }
create(data) { create(data, cache) {
const guild = this.guild; super.create(data, cache, { extras: [this.guild] });
}
const existing = guild.emojis.get(data.id); /**
if (existing) return existing; * Data that can be resolved into an Emoji object. This can be:
* * A custom emoji ID
* * An Emoji object
* * A ReactionEmoji object
* @typedef {Snowflake|Emoji|ReactionEmoji} EmojiResolvable
*/
const emoji = new Emoji(guild, data); /**
guild.emojis.set(emoji.id, emoji); * Resolves a EmojiResolvable to a Emoji object.
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
* @returns {?Emoji}
*/
resolve(emoji) {
if (emoji instanceof ReactionEmoji) return super.resolve(emoji.id);
return super.resolve(emoji);
}
return emoji; /**
* Resolves a EmojiResolvable to a Emoji ID string.
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
* @returns {?string}
*/
resolveID(emoji) {
if (emoji instanceof ReactionEmoji) return emoji.id;
return super.resolveID(emoji);
}
/**
* Data that can be resolved to give an emoji identifier. This can be:
* * The unicode representation of an emoji
* * An EmojiResolveable
* @typedef {string|EmojiResolvable} EmojiIdentifierResolvable
*/
/**
* Resolves an EmojiResolvable to an emoji identifier.
* @param {EmojiIdentifierResolvable} emoji The emoji resolvable to resolve
* @returns {?string}
*/
resolveIdentifier(emoji) {
const emojiResolveable = this.resolve(emoji);
if (emojiResolveable) return emojiResolveable.identifier;
if (typeof emoji === 'string') {
if (!emoji.includes('%')) return encodeURIComponent(emoji);
else return emoji;
}
return null;
} }
} }

View File

@@ -1,5 +1,6 @@
const DataStore = require('./DataStore'); const DataStore = require('./DataStore');
const Channel = require('../structures/Channel'); const Channel = require('../structures/Channel');
const GuildChannel = require('../structures/GuildChannel');
/** /**
* Stores guild channels. * Stores guild channels.
@@ -8,7 +9,7 @@ const Channel = require('../structures/Channel');
*/ */
class GuildChannelStore extends DataStore { class GuildChannelStore extends DataStore {
constructor(guild, iterable) { constructor(guild, iterable) {
super(guild.client, iterable); super(guild.client, iterable, GuildChannel);
this.guild = guild; this.guild = guild;
} }
@@ -18,6 +19,29 @@ class GuildChannelStore extends DataStore {
return Channel.create(this.client, data, this.guild); return Channel.create(this.client, data, this.guild);
} }
/**
* Data that can be resolved to give a Channel object. This can be:
* * A GuildChannel object
* * A Snowflake
* @typedef {Channel|Snowflake} GuildChannelResolvable
*/
/**
* Resolves a GuildChannelResolvable to a Channel object.
* @method resolve
* @memberof GuildChannelStore
* @param {GuildChannelResolvable} channel The GuildChannel resolvable to resolve
* @returns {?Channel}
*/
/**
* Resolves a GuildChannelResolvable to a channel ID string.
* @method resolveID
* @memberof GuildChannelStore
* @param {GuildChannelResolvable} channel The GuildChannel resolvable to resolve
* @returns {?string}
*/
} }
module.exports = GuildChannelStore; module.exports = GuildChannelStore;

View File

@@ -10,18 +10,44 @@ const { Error } = require('../errors');
*/ */
class GuildMemberStore extends DataStore { class GuildMemberStore extends DataStore {
constructor(guild, iterable) { constructor(guild, iterable) {
super(guild.client, iterable); super(guild.client, iterable, GuildMember);
this.guild = guild; this.guild = guild;
} }
create(data, cache = true) { create(data, cache) {
const existing = this.get(data.user.id); return super.create(data, cache, { extras: [this.guild] });
if (existing) return existing; }
const member = new GuildMember(this.guild, data); /**
if (cache) this.set(member.id, member); * Data that resolves to give a GuildMember object. This can be:
* * A GuildMember object
* * A User resolvable
* @typedef {GuildMember|UserResolveable} GuildMemberResolvable
*/
return member; /**
* Resolves a GuildMemberResolvable to a GuildMember object.
* @param {GuildMemberResolvable} member The user that is part of the guild
* @returns {?GuildMember}
*/
resolve(member) {
const memberResolveable = super.resolve(member);
if (memberResolveable) return memberResolveable;
const userResolveable = this.client.users.resolveID(member);
if (userResolveable) return super.resolve(userResolveable);
return null;
}
/**
* Resolves a GuildMemberResolvable to an member ID string.
* @param {GuildMemberResolvable} member The user that is part of the guild
* @returns {?string}
*/
resolveID(member) {
const memberResolveable = super.resolveID(member);
if (memberResolveable) return memberResolveable;
const userResolveable = this.client.users.resolveID(member);
return this.has(userResolveable) ? userResolveable : null;
} }
/** /**
@@ -64,10 +90,10 @@ class GuildMemberStore extends DataStore {
*/ */
fetch(options) { fetch(options) {
if (!options) return this._fetchMany(); if (!options) return this._fetchMany();
const user = this.client.resolver.resolveUserID(options); const user = this.resolveID(options);
if (user) return this._fetchSingle({ user, cache: true }); if (user) return this._fetchSingle({ user, cache: true });
if (options.user) { if (options.user) {
options.user = this.client.resolver.resolveUserID(options.user); options.user = this.resolveID(options.user);
if (options.user) return this._fetchSingle(options); if (options.user) return this._fetchSingle(options);
} }
return this._fetchMany(options); return this._fetchMany(options);

View File

@@ -1,20 +1,38 @@
const DataStore = require('./DataStore'); const DataStore = require('./DataStore');
const Guild = require('../structures/Guild'); const Guild = require('../structures/Guild');
/** /**
* Stores guilds. * Stores guilds.
* @private * @private
* @extends {DataStore} * @extends {DataStore}
*/ */
class GuildStore extends DataStore { class GuildStore extends DataStore {
create(data, cache = true) { constructor(client, iterable) {
const existing = this.get(data.id); super(client, iterable, Guild);
if (existing) return existing;
const guild = new Guild(this.client, data);
if (cache) this.set(guild.id, guild);
return guild;
} }
/**
* Data that resolves to give a Guild object. This can be:
* * A Guild object
* * A Snowflake
* @typedef {Guild|Snowflake} GuildResolvable
*/
/**
* Resolves a GuildResolvable to a Guild object.
* @method resolve
* @memberof GuildStore
* @param {GuildResolvable} guild The guild resolvable to identify
* @returns {?Guild}
*/
/**
* Resolves a GuildResolvable to a Guild ID string.
* @method resolveID
* @memberof GuildStore
* @param {GuildResolvable} guild The guild resolvable to identify
* @returns {?string}
*/
} }
module.exports = GuildStore; module.exports = GuildStore;

View File

@@ -1,7 +1,7 @@
const DataStore = require('./DataStore'); const DataStore = require('./DataStore');
const Collection = require('../util/Collection'); const Collection = require('../util/Collection');
const Message = require('../structures/Message');
const { Error } = require('../errors'); const { Error } = require('../errors');
let Message;
/** /**
* Stores messages for text-based channels. * Stores messages for text-based channels.
@@ -9,19 +9,12 @@ let Message;
*/ */
class MessageStore extends DataStore { class MessageStore extends DataStore {
constructor(channel, iterable) { constructor(channel, iterable) {
super(channel.client, iterable); super(channel.client, iterable, Message);
this.channel = channel; this.channel = channel;
Message = require('../structures/Message');
} }
create(data, cache = true) { create(data, cache) {
const existing = this.get(data.id); return super.create(data, cache, { extras: [this.channel] });
if (existing) return existing;
const message = new Message(this.client.channels.get(data.channel_id), data, this.client);
if (cache) this.set(message.id, message);
return message;
} }
set(key, value) { set(key, value) {
@@ -62,7 +55,7 @@ class MessageStore extends DataStore {
/** /**
* Fetches the pinned messages of this channel and returns a collection of them. * Fetches the pinned messages of this channel and returns a collection of them.
* <info>The returned Collection does not contain the reactions of the messages. * <info>The returned Collection does not contain the reactions of the messages.
* Those need to be fetched seperately.</info> * Those need to be fetched seperately.</info>
* @returns {Promise<Collection<Snowflake, Message>>} * @returns {Promise<Collection<Snowflake, Message>>}
*/ */
@@ -95,6 +88,30 @@ class MessageStore extends DataStore {
return messages; return messages;
}); });
} }
/**
* Data that can be resolved to a Message object. This can be:
* * A Message
* * A Snowflake
* @typedef {Message|Snowflake} MessageResolvable
*/
/**
* Resolves a MessageResolvable to a Message object.
* @method resolve
* @memberof MessageStore
* @param {MessageResolvable} message The message resolvable to resolve
* @returns {?Message}
*/
/**
* Resolves a MessageResolvable to a Message ID string.
* @method MessageStore
* @memberof PresenceStore
* @param {MessageResolvable} message The message resolvable to resolve
* @returns {?string}
*/
} }
module.exports = MessageStore; module.exports = MessageStore;

View File

@@ -2,13 +2,46 @@ const DataStore = require('./DataStore');
const { Presence } = require('../structures/Presence'); const { Presence } = require('../structures/Presence');
class PresenceStore extends DataStore { class PresenceStore extends DataStore {
create(data) { constructor(client, iterable) {
if (this.has(data.user.id)) { super(client, iterable, Presence);
this.get(data.user.id).patch(data); }
} else {
this.set(data.user.id, new Presence(this.client, data)); create(data, cache) {
} const existing = this.get(data.user.id);
return this.get(data.user.id); return existing ? existing.patch(data) : super.create(data, cache, { id: data.user.id });
}
/**
* Data that can be resolved to a Presence object. This can be:
* * A Presence
* * A UserResolveable
* * A Snowflake
* @typedef {Presence|UserResolveable|Snowflake} PresenceResolvable
*/
/**
* Resolves a PresenceResolvable to a Presence object.
* @param {PresenceResolvable} presence The presence resolvable to resolve
* @returns {?Presence}
*/
resolve(presence) {
const presenceResolveable = super.resolve(presence);
if (presenceResolveable) return presenceResolveable;
const UserResolveable = this.client.users.resolveID(presence);
return super.resolve(UserResolveable) || null;
}
/**
* Resolves a PresenceResolvable to a Presence ID string.
* @param {PresenceResolvable} presence The presence resolvable to resolve
* @returns {?string}
*/
resolveID(presence) {
const presenceResolveable = super.resolveID(presence);
if (presenceResolveable) return presenceResolveable;
const userResolveable = this.client.users.resolveID(presence);
return this.has(userResolveable) ? userResolveable : null;
} }
} }

View File

@@ -1,5 +1,6 @@
const DataStore = require('./DataStore'); const DataStore = require('./DataStore');
const MessageReaction = require('../structures/MessageReaction'); const MessageReaction = require('../structures/MessageReaction');
/** /**
* Stores reactions. * Stores reactions.
* @private * @private
@@ -7,21 +8,37 @@ const MessageReaction = require('../structures/MessageReaction');
*/ */
class ReactionStore extends DataStore { class ReactionStore extends DataStore {
constructor(message, iterable) { constructor(message, iterable) {
super(message.client, iterable); super(message.client, iterable, MessageReaction);
this.message = message; this.message = message;
} }
create(data) { create(data, cache) {
const emojiID = data.emoji.id || decodeURIComponent(data.emoji.name); data.emoji.id = data.emoji.id || decodeURIComponent(data.emoji.name);
return super.create(data, cache, { id: data.emoji.id, extras: [this.message] });
const existing = this.get(emojiID);
if (existing) return existing;
const reaction = new MessageReaction(this.message, data.emoji, data.count, data.me);
this.set(emojiID, reaction);
return reaction;
} }
/**
* Data that can be resolved to a MessageReaction object. This can be:
* * A MessageReaction
* * A Snowflake
* @typedef {MessageReaction|Snowflake} MessageReactionResolvable
*/
/**
* Resolves a MessageReactionResolvable to a MessageReaction object.
* @method resolve
* @memberof ReactionStore
* @param {MessageReactionResolvable} reaction The MessageReaction to resolve
* @returns {?MessageReaction}
*/
/**
* Resolves a MessageReactionResolvable to a MessageReaction ID string.
* @method resolveID
* @memberof ReactionStore
* @param {MessageReactionResolvable} role The role resolvable to resolve
* @returns {?string}
*/
} }
module.exports = ReactionStore; module.exports = ReactionStore;

View File

@@ -1,5 +1,6 @@
const DataStore = require('./DataStore'); const DataStore = require('./DataStore');
const Role = require('../structures/Role'); const Role = require('../structures/Role');
/** /**
* Stores roles. * Stores roles.
* @private * @private
@@ -7,19 +8,36 @@ const Role = require('../structures/Role');
*/ */
class RoleStore extends DataStore { class RoleStore extends DataStore {
constructor(guild, iterable) { constructor(guild, iterable) {
super(guild.client, iterable); super(guild.client, iterable, Role);
this.guild = guild; this.guild = guild;
} }
create(data) { create(data, cache) {
const existing = this.get(data.id); return super.create(data, cache, { extras: [this.guild] });
if (existing) return existing;
const role = new Role(this.guild, data);
this.set(role.id, role);
return role;
} }
/**
* Data that can be resolved to a Role object. This can be:
* * A Role
* * A Snowflake
* @typedef {Role|Snowflake} RoleResolvable
*/
/**
* Resolves a RoleResolvable to a Role object.
* @method resolve
* @memberof RoleStore
* @param {RoleResolvable} role The role resolvable to resolve
* @returns {?Role}
*/
/**
* Resolves a RoleResolvable to a role ID string.
* @method resolveID
* @memberof RoleStore
* @param {RoleResolvable} role The role resolvable to resolve
* @returns {?string}
*/
} }
module.exports = RoleStore; module.exports = RoleStore;

View File

@@ -1,18 +1,46 @@
const DataStore = require('./DataStore'); const DataStore = require('./DataStore');
const User = require('../structures/User'); const User = require('../structures/User');
const GuildMember = require('../structures/GuildMember');
const Message = require('../structures/Message');
/** /**
* A data store to store User models. * A data store to store User models.
* @extends {DataStore} * @extends {DataStore}
*/ */
class UserStore extends DataStore { class UserStore extends DataStore {
create(data, cache = true) { constructor(client, iterable) {
const existing = this.get(data.id); super(client, iterable, User);
if (existing) return existing; }
const user = new User(this.client, data); /**
if (cache) this.set(user.id, user); * Data that resolves to give a User object. This can be:
return user; * * A User object
* * A Snowflake
* * A Message object (resolves to the message author)
* * A GuildMember object
* @typedef {User|Snowflake|Message|GuildMember} UserResolvable
*/
/**
* Resolves a UserResolvable to a User object.
* @param {UserResolvable} user The UserResolvable to identify
* @returns {?User}
*/
resolve(user) {
if (user instanceof GuildMember) return user.user;
if (user instanceof Message) return user.author;
return super.resolve(user);
}
/**
* Resolves a UserResolvable to a user ID string.
* @param {UserResolvable} user The UserResolvable to identify
* @returns {?string}
*/
resolveID(user) {
if (user instanceof GuildMember) return user.user.id;
if (user instanceof Message) return user.author.id;
return super.resolveID(user);
} }
/** /**

View File

@@ -1,5 +1,6 @@
const Snowflake = require('../util/Snowflake'); const Snowflake = require('../util/Snowflake');
const Constants = require('../util/Constants'); const Constants = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const Base = require('./Base'); const Base = require('./Base');
/** /**
@@ -165,7 +166,7 @@ class ClientApplication extends Base {
* @returns {Promise} * @returns {Promise}
*/ */
createAsset(name, data, type) { createAsset(name, data, type) {
return this.client.resolveBase64(data).then(b64 => return DataResolver.resolveBase64(data).then(b64 =>
this.client.api.applications(this.id).assets.post({ data: { this.client.api.applications(this.id).assets.post({ data: {
name, name,
data: b64, data: b64,

View File

@@ -4,6 +4,7 @@ const ClientUserSettings = require('./ClientUserSettings');
const ClientUserGuildSettings = require('./ClientUserGuildSettings'); const ClientUserGuildSettings = require('./ClientUserGuildSettings');
const Constants = require('../util/Constants'); const Constants = require('../util/Constants');
const Util = require('../util/Util'); const Util = require('../util/Util');
const DataResolver = require('../util/DataResolver');
const Guild = require('./Guild'); const Guild = require('./Guild');
/** /**
@@ -177,7 +178,7 @@ class ClientUser extends User {
* .catch(console.error); * .catch(console.error);
*/ */
async setAvatar(avatar) { async setAvatar(avatar) {
return this.edit({ avatar: await this.client.resolver.resolveImage(avatar) }); return this.edit({ avatar: await DataResolver.resolveImage(avatar, this.client.browser) });
} }
/** /**
@@ -293,7 +294,7 @@ class ClientUser extends User {
); );
} }
return this.client.resolver.resolveImage(icon) return DataResolver.resolveImage(icon, this.client.browser)
.then(data => this.createGuild(name, { region, icon: data || null })); .then(data => this.createGuild(name, { region, icon: data || null }));
} }
@@ -320,7 +321,7 @@ class ClientUser extends User {
if (r.nick) o[r.user ? r.user.id : r.id] = r.nick; if (r.nick) o[r.user ? r.user.id : r.id] = r.nick;
return o; return o;
}, {}), }, {}),
} : { recipients: recipients.map(u => this.client.resolver.resolveUserID(u.user || u.id)) }; } : { recipients: recipients.map(u => this.client.users.resolveID(u.user || u.id)) };
return this.client.api.users('@me').channels.post({ data }) return this.client.api.users('@me').channels.post({ data })
.then(res => this.client.channels.create(res)); .then(res => this.client.channels.create(res));
} }

View File

@@ -8,8 +8,8 @@ const { TypeError } = require('../errors');
* @extends {Base} * @extends {Base}
*/ */
class Emoji extends Base { class Emoji extends Base {
constructor(guild, data) { constructor(client, data, guild) {
super(guild.client); super(client);
/** /**
* The guild this emoji is part of * The guild this emoji is part of
@@ -152,7 +152,7 @@ class Emoji extends Base {
addRestrictedRoles(roles) { addRestrictedRoles(roles) {
const newRoles = new Collection(this.roles); const newRoles = new Collection(this.roles);
for (let role of roles instanceof Collection ? roles.values() : roles) { for (let role of roles instanceof Collection ? roles.values() : roles) {
role = this.client.resolver.resolveRole(this.guild, role); role = this.guild.roles.resolve(role);
if (!role) { if (!role) {
return Promise.reject(new TypeError('INVALID_TYPE', 'roles', return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
'Array or Collection of Roles or Snowflakes', true)); 'Array or Collection of Roles or Snowflakes', true));
@@ -179,7 +179,7 @@ class Emoji extends Base {
removeRestrictedRoles(roles) { removeRestrictedRoles(roles) {
const newRoles = new Collection(this.roles); const newRoles = new Collection(this.roles);
for (let role of roles instanceof Collection ? roles.values() : roles) { for (let role of roles instanceof Collection ? roles.values() : roles) {
role = this.client.resolver.resolveRole(this.guild, role); role = this.guild.roles.resolve(role);
if (!role) { if (!role) {
return Promise.reject(new TypeError('INVALID_TYPE', 'roles', return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
'Array or Collection of Roles or Snowflakes', true)); 'Array or Collection of Roles or Snowflakes', true));

View File

@@ -1,6 +1,7 @@
const Channel = require('./Channel'); const Channel = require('./Channel');
const TextBasedChannel = require('./interfaces/TextBasedChannel'); const TextBasedChannel = require('./interfaces/TextBasedChannel');
const Collection = require('../util/Collection'); const Collection = require('../util/Collection');
const DataResolver = require('../util/DataResolver');
const MessageStore = require('../stores/MessageStore'); const MessageStore = require('../stores/MessageStore');
/* /*
@@ -160,7 +161,7 @@ class GroupDMChannel extends Channel {
* @returns {Promise<GroupDMChannel>} * @returns {Promise<GroupDMChannel>}
*/ */
async setIcon(icon) { async setIcon(icon) {
return this.edit({ icon: await this.client.resolver.resolveImage(icon) }); return this.edit({ icon: await DataResolver.resolveImage(icon, this.client.browser) });
} }
/** /**
@@ -182,7 +183,7 @@ class GroupDMChannel extends Channel {
* @returns {Promise<GroupDMChannel>} * @returns {Promise<GroupDMChannel>}
*/ */
addUser({ user, accessToken, nick }) { addUser({ user, accessToken, nick }) {
const id = this.client.resolver.resolveUserID(user); const id = this.client.users.resolveID(user);
const data = this.client.user.bot ? const data = this.client.user.bot ?
{ nick, access_token: accessToken } : { nick, access_token: accessToken } :
{ recipient: id }; { recipient: id };
@@ -196,7 +197,7 @@ class GroupDMChannel extends Channel {
* @returns {Promise<GroupDMChannel>} * @returns {Promise<GroupDMChannel>}
*/ */
removeUser(user) { removeUser(user) {
const id = this.client.resolver.resolveUserID(user); const id = this.client.users.resolveID(user);
return this.client.api.channels[this.id].recipients[id].delete() return this.client.api.channels[this.id].recipients[id].delete()
.then(() => this); .then(() => this);
} }

View File

@@ -9,6 +9,7 @@ const VoiceRegion = require('./VoiceRegion');
const Constants = require('../util/Constants'); const Constants = require('../util/Constants');
const Collection = require('../util/Collection'); const Collection = require('../util/Collection');
const Util = require('../util/Util'); const Util = require('../util/Util');
const DataResolver = require('../util/DataResolver');
const Snowflake = require('../util/Snowflake'); const Snowflake = require('../util/Snowflake');
const Permissions = require('../util/Permissions'); const Permissions = require('../util/Permissions');
const Shared = require('./shared'); const Shared = require('./shared');
@@ -431,7 +432,7 @@ class Guild extends Base {
* const member = guild.member(message.author); * const member = guild.member(message.author);
*/ */
member(user) { member(user) {
return this.client.resolver.resolveGuildMember(this, user); return this.members.resolve(user);
} }
/** /**
@@ -511,7 +512,7 @@ class Guild extends Base {
before: options.before, before: options.before,
after: options.after, after: options.after,
limit: options.limit, limit: options.limit,
user_id: this.client.resolver.resolveUserID(options.user), user_id: this.client.users.resolveID(options.user),
action_type: options.type, action_type: options.type,
} }) } })
.then(data => GuildAuditLogs.build(this, data)); .then(data => GuildAuditLogs.build(this, data));
@@ -536,7 +537,7 @@ class Guild extends Base {
if (options.roles) { if (options.roles) {
const roles = []; const roles = [];
for (let role of options.roles instanceof Collection ? options.roles.values() : options.roles) { for (let role of options.roles instanceof Collection ? options.roles.values() : options.roles) {
role = this.client.resolver.resolveRole(this, role); role = this.roles.resolve(role);
if (!role) { if (!role) {
return Promise.reject(new TypeError('INVALID_TYPE', 'options.roles', return Promise.reject(new TypeError('INVALID_TYPE', 'options.roles',
'Array or Collection of Roles or Snowflakes', true)); 'Array or Collection of Roles or Snowflakes', true));
@@ -601,14 +602,14 @@ class Guild extends Base {
if (data.region) _data.region = data.region; if (data.region) _data.region = data.region;
if (typeof data.verificationLevel !== 'undefined') _data.verification_level = Number(data.verificationLevel); if (typeof data.verificationLevel !== 'undefined') _data.verification_level = Number(data.verificationLevel);
if (typeof data.afkChannel !== 'undefined') { if (typeof data.afkChannel !== 'undefined') {
_data.afk_channel_id = this.client.resolver.resolveChannelID(data.afkChannel); _data.afk_channel_id = this.client.channels.resolveID(data.afkChannel);
} }
if (typeof data.systemChannel !== 'undefined') { if (typeof data.systemChannel !== 'undefined') {
_data.system_channel_id = this.client.resolver.resolveChannelID(data.systemChannel); _data.system_channel_id = this.client.channels.resolveID(data.systemChannel);
} }
if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout); if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout);
if (typeof data.icon !== 'undefined') _data.icon = data.icon; if (typeof data.icon !== 'undefined') _data.icon = data.icon;
if (data.owner) _data.owner_id = this.client.resolver.resolveUser(data.owner).id; if (data.owner) _data.owner_id = this.client.users.resolve(data.owner).id;
if (data.splash) _data.splash = data.splash; if (data.splash) _data.splash = data.splash;
if (typeof data.explicitContentFilter !== 'undefined') { if (typeof data.explicitContentFilter !== 'undefined') {
_data.explicit_content_filter = Number(data.explicitContentFilter); _data.explicit_content_filter = Number(data.explicitContentFilter);
@@ -724,7 +725,7 @@ class Guild extends Base {
* .catch(console.error); * .catch(console.error);
*/ */
async setIcon(icon, reason) { async setIcon(icon, reason) {
return this.edit({ icon: await this.client.resolver.resolveImage(icon), reason }); return this.edit({ icon: await DataResolver.resolveImage(icon, this.client.browser), reason });
} }
/** /**
@@ -754,7 +755,7 @@ class Guild extends Base {
* .catch(console.error); * .catch(console.error);
*/ */
async setSplash(splash, reason) { async setSplash(splash, reason) {
return this.edit({ splash: await this.client.resolver.resolveImage(splash), reason }); return this.edit({ splash: await DataResolver.resolveImage(splash, this.client.browser), reason });
} }
/** /**
@@ -815,14 +816,14 @@ class Guild extends Base {
*/ */
ban(user, options = { days: 0 }) { ban(user, options = { days: 0 }) {
if (options.days) options['delete-message-days'] = options.days; if (options.days) options['delete-message-days'] = options.days;
const id = this.client.resolver.resolveUserID(user); const id = this.client.users.resolveID(user);
if (!id) return Promise.reject(new Error('BAN_RESOLVE_ID', true)); if (!id) return Promise.reject(new Error('BAN_RESOLVE_ID', true));
return this.client.api.guilds(this.id).bans[id].put({ query: options }) return this.client.api.guilds(this.id).bans[id].put({ query: options })
.then(() => { .then(() => {
if (user instanceof GuildMember) return user; if (user instanceof GuildMember) return user;
const _user = this.client.resolver.resolveUser(id); const _user = this.client.users.resolve(id);
if (_user) { if (_user) {
const member = this.client.resolver.resolveGuildMember(this, _user); const member = this.members.resolve(_user);
return member || _user; return member || _user;
} }
return id; return id;
@@ -841,7 +842,7 @@ class Guild extends Base {
* .catch(console.error); * .catch(console.error);
*/ */
unban(user, reason) { unban(user, reason) {
const id = this.client.resolver.resolveUserID(user); const id = this.client.users.resolverID(user);
if (!id) throw new Error('BAN_RESOLVE_ID'); if (!id) throw new Error('BAN_RESOLVE_ID');
return this.client.api.guilds(this.id).bans[id].delete({ reason }) return this.client.api.guilds(this.id).bans[id].delete({ reason })
.then(() => user); .then(() => user);
@@ -910,12 +911,12 @@ class Guild extends Base {
if (allow instanceof Array) allow = Permissions.resolve(allow); if (allow instanceof Array) allow = Permissions.resolve(allow);
if (deny instanceof Array) deny = Permissions.resolve(deny); if (deny instanceof Array) deny = Permissions.resolve(deny);
const role = this.client.resolver.resolveRole(this, overwrite.id); const role = this.roles.resolve(overwrite.id);
if (role) { if (role) {
overwrite.id = role.id; overwrite.id = role.id;
overwrite.type = 'role'; overwrite.type = 'role';
} else { } else {
overwrite.id = this.client.resolver.resolveUserID(overwrite.id); overwrite.id = this.client.users.resolveID(overwrite.id);
overwrite.type = 'member'; overwrite.type = 'member';
} }
@@ -988,7 +989,7 @@ class Guild extends Base {
*/ */
setChannelPositions(channelPositions) { setChannelPositions(channelPositions) {
const updatedChannels = channelPositions.map(r => ({ const updatedChannels = channelPositions.map(r => ({
id: this.client.resolver.resolveChannelID(r.channel), id: this.client.channels.resolveID(r.channel),
position: r.position, position: r.position,
})); }));
@@ -1063,7 +1064,7 @@ class Guild extends Base {
if (roles) { if (roles) {
data.roles = []; data.roles = [];
for (let role of roles instanceof Collection ? roles.values() : roles) { for (let role of roles instanceof Collection ? roles.values() : roles) {
role = this.client.resolver.resolveRole(this, role); role = this.roles.resolve(role);
if (!role) { if (!role) {
return Promise.reject(new TypeError('INVALID_TYPE', 'options.roles', return Promise.reject(new TypeError('INVALID_TYPE', 'options.roles',
'Array or Collection of Roles or Snowflakes', true)); 'Array or Collection of Roles or Snowflakes', true));
@@ -1076,7 +1077,7 @@ class Guild extends Base {
.then(emoji => this.client.actions.GuildEmojiCreate.handle(this, emoji).emoji); .then(emoji => this.client.actions.GuildEmojiCreate.handle(this, emoji).emoji);
} }
return this.client.resolver.resolveImage(attachment) return DataResolver.resolveImage(attachment, this.client.browser)
.then(image => this.createEmoji(image, name, { roles, reason })); .then(image => this.createEmoji(image, name, { roles, reason }));
} }

View File

@@ -66,7 +66,7 @@ class GuildChannel extends Channel {
* @returns {?Permissions} * @returns {?Permissions}
*/ */
permissionsFor(member) { permissionsFor(member) {
member = this.client.resolver.resolveGuildMember(this.guild, member); member = this.guild.members.resolve(member);
if (!member) return null; if (!member) return null;
if (member.id === this.guild.ownerID) return new Permissions(Permissions.ALL); if (member.id === this.guild.ownerID) return new Permissions(Permissions.ALL);
@@ -101,7 +101,7 @@ class GuildChannel extends Channel {
} }
overwritesFor(member, verified = false, roles = null) { overwritesFor(member, verified = false, roles = null) {
if (!verified) member = this.client.resolver.resolveGuildMember(this.guild, member); if (!verified) member = this.guild.members.resolve(member);
if (!member) return []; if (!member) return [];
roles = roles || member.roles; roles = roles || member.roles;
@@ -158,13 +158,13 @@ class GuildChannel extends Channel {
deny: 0, deny: 0,
}; };
if (userOrRole instanceof Role) { const role = this.guild.roles.get(userOrRole);
payload.type = 'role';
} else if (this.guild.roles.has(userOrRole)) { if (role || userOrRole instanceof Role) {
userOrRole = this.guild.roles.get(userOrRole); userOrRole = role || userOrRole;
payload.type = 'role'; payload.type = 'role';
} else { } else {
userOrRole = this.client.resolver.resolveUser(userOrRole); userOrRole = this.client.users.resolve(userOrRole);
payload.type = 'member'; payload.type = 'member';
if (!userOrRole) return Promise.reject(new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role', true)); if (!userOrRole) return Promise.reject(new TypeError('INVALID_TYPE', 'parameter', 'User nor a Role', true));
} }

View File

@@ -12,8 +12,8 @@ const { Error, TypeError } = require('../errors');
* @extends {Base} * @extends {Base}
*/ */
class GuildMember extends Base { class GuildMember extends Base {
constructor(guild, data) { constructor(client, data, guild) {
super(guild.client); super(client);
/** /**
* The guild that this member is part of * The guild that this member is part of
@@ -291,7 +291,7 @@ class GuildMember extends Base {
* @returns {?Permissions} * @returns {?Permissions}
*/ */
permissionsIn(channel) { permissionsIn(channel) {
channel = this.client.resolver.resolveChannel(channel); channel = this.client.channels.resolve(channel);
if (!channel || !channel.guild) throw new Error('GUILD_CHANNEL_RESOLVE'); if (!channel || !channel.guild) throw new Error('GUILD_CHANNEL_RESOLVE');
return channel.permissionsFor(this); return channel.permissionsFor(this);
} }
@@ -342,7 +342,7 @@ class GuildMember extends Base {
*/ */
edit(data, reason) { edit(data, reason) {
if (data.channel) { if (data.channel) {
data.channel_id = this.client.resolver.resolveChannel(data.channel).id; data.channel_id = this.client.channels.resolve(data.channel).id;
data.channel = null; data.channel = null;
} }
if (data.roles) data.roles = data.roles.map(role => role instanceof Role ? role.id : role); if (data.roles) data.roles = data.roles.map(role => role instanceof Role ? role.id : role);
@@ -412,7 +412,7 @@ class GuildMember extends Base {
* @returns {Promise<GuildMember>} * @returns {Promise<GuildMember>}
*/ */
addRole(role, reason) { addRole(role, reason) {
role = this.client.resolver.resolveRole(this.guild, role); role = this.guild.roles.resolve(role);
if (!role) return Promise.reject(new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake')); if (!role) return Promise.reject(new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake'));
if (this._roles.includes(role.id)) return Promise.resolve(this); if (this._roles.includes(role.id)) return Promise.resolve(this);
return this.client.api.guilds(this.guild.id).members(this.user.id).roles(role.id) return this.client.api.guilds(this.guild.id).members(this.user.id).roles(role.id)
@@ -433,7 +433,7 @@ class GuildMember extends Base {
addRoles(roles, reason) { addRoles(roles, reason) {
let allRoles = this._roles.slice(); let allRoles = this._roles.slice();
for (let role of roles instanceof Collection ? roles.values() : roles) { for (let role of roles instanceof Collection ? roles.values() : roles) {
role = this.client.resolver.resolveRole(this.guild, role); role = this.guild.roles.resolve(role);
if (!role) { if (!role) {
return Promise.reject(new TypeError('INVALID_TYPE', 'roles', return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
'Array or Collection of Roles or Snowflakes', true)); 'Array or Collection of Roles or Snowflakes', true));
@@ -450,7 +450,7 @@ class GuildMember extends Base {
* @returns {Promise<GuildMember>} * @returns {Promise<GuildMember>}
*/ */
removeRole(role, reason) { removeRole(role, reason) {
role = this.client.resolver.resolveRole(this.guild, role); role = this.guild.roles.resolve(role);
if (!role) return Promise.reject(new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake')); if (!role) return Promise.reject(new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake'));
if (!this._roles.includes(role.id)) return Promise.resolve(this); if (!this._roles.includes(role.id)) return Promise.resolve(this);
return this.client.api.guilds(this.guild.id).members(this.user.id).roles(role.id) return this.client.api.guilds(this.guild.id).members(this.user.id).roles(role.id)
@@ -472,7 +472,7 @@ class GuildMember extends Base {
removeRoles(roles, reason) { removeRoles(roles, reason) {
const allRoles = this._roles.slice(); const allRoles = this._roles.slice();
for (let role of roles instanceof Collection ? roles.values() : roles) { for (let role of roles instanceof Collection ? roles.values() : roles) {
role = this.client.resolver.resolveRole(this.guild, role); role = this.guild.roles.resolve(role);
if (!role) { if (!role) {
return Promise.reject(new TypeError('INVALID_TYPE', 'roles', return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
'Array or Collection of Roles or Snowflakes', true)); 'Array or Collection of Roles or Snowflakes', true));

View File

@@ -17,7 +17,7 @@ let GuildMember;
* @extends {Base} * @extends {Base}
*/ */
class Message extends Base { class Message extends Base {
constructor(channel, data, client) { constructor(client, data, channel) {
super(client); super(client);
/** /**
@@ -393,7 +393,7 @@ class Message extends Base {
// Add the reply prefix // Add the reply prefix
if (reply && this.channel.type !== 'dm') { if (reply && this.channel.type !== 'dm') {
const id = this.client.resolver.resolveUserID(reply); const id = this.client.users.resolveID(reply);
const mention = `<@${reply instanceof GuildMember && reply.nickname ? '!' : ''}${id}>`; const mention = `<@${reply instanceof GuildMember && reply.nickname ? '!' : ''}${id}>`;
content = `${mention}${content ? `, ${content}` : ''}`; content = `${mention}${content ? `, ${content}` : ''}`;
} }
@@ -427,11 +427,11 @@ class Message extends Base {
/** /**
* Add a reaction to the message. * Add a reaction to the message.
* @param {string|Emoji|ReactionEmoji} emoji The emoji to react with * @param {EmojiIdentifierResolveable} emoji The emoji to react with
* @returns {Promise<MessageReaction>} * @returns {Promise<MessageReaction>}
*/ */
react(emoji) { react(emoji) {
emoji = this.client.resolver.resolveEmojiIdentifier(emoji); emoji = this.client.emojis.resolveIdentifier(emoji);
if (!emoji) throw new TypeError('EMOJI_TYPE'); if (!emoji) throw new TypeError('EMOJI_TYPE');
return this.client.api.channels(this.channel.id).messages(this.id).reactions(emoji, '@me') return this.client.api.channels(this.channel.id).messages(this.id).reactions(emoji, '@me')

View File

@@ -7,7 +7,7 @@ const { Error } = require('../errors');
* Represents a reaction to a message. * Represents a reaction to a message.
*/ */
class MessageReaction { class MessageReaction {
constructor(message, emoji, count, me) { constructor(client, data, message) {
/** /**
* The message that this reaction refers to * The message that this reaction refers to
* @type {Message} * @type {Message}
@@ -18,13 +18,13 @@ class MessageReaction {
* Whether the client has given this reaction * Whether the client has given this reaction
* @type {boolean} * @type {boolean}
*/ */
this.me = me; this.me = data.me;
/** /**
* The number of people that have given the same reaction * The number of people that have given the same reaction
* @type {number} * @type {number}
*/ */
this.count = count || 0; this.count = data.count || 0;
/** /**
* The users that have given this reaction, mapped by their ID * The users that have given this reaction, mapped by their ID
@@ -32,7 +32,7 @@ class MessageReaction {
*/ */
this.users = new Collection(); this.users = new Collection();
this._emoji = new ReactionEmoji(this, emoji.name, emoji.id); this._emoji = new ReactionEmoji(this, data.emoji.name, data.emoji.id);
} }
/** /**
@@ -62,7 +62,7 @@ class MessageReaction {
* @returns {Promise<MessageReaction>} * @returns {Promise<MessageReaction>}
*/ */
remove(user = this.message.client.user) { remove(user = this.message.client.user) {
const userID = this.message.client.resolver.resolveUserID(user); const userID = this.message.client.users.resolveID(user);
if (!userID) return Promise.reject(new Error('REACTION_RESOLVE_USER')); if (!userID) return Promise.reject(new Error('REACTION_RESOLVE_USER'));
return this.message.client.api.channels[this.message.channel.id].messages[this.message.id] return this.message.client.api.channels[this.message.channel.id].messages[this.message.id]
.reactions[this.emoji.identifier][userID === this.message.client.user.id ? '@me' : userID] .reactions[this.emoji.identifier][userID === this.message.client.user.id ? '@me' : userID]

View File

@@ -26,6 +26,8 @@ class Presence {
* @type {?Activity} * @type {?Activity}
*/ */
this.activity = activity ? new Activity(this, activity) : null; this.activity = activity ? new Activity(this, activity) : null;
return this;
} }
_clone() { _clone() {

View File

@@ -9,8 +9,8 @@ const { TypeError } = require('../errors');
* @extends {Base} * @extends {Base}
*/ */
class Role extends Base { class Role extends Base {
constructor(guild, data) { constructor(client, data, guild) {
super(guild.client); super(client);
/** /**
* The guild that the role belongs to * The guild that the role belongs to
@@ -171,7 +171,7 @@ class Role extends Base {
* positive number if the this one is higher (other's is lower), 0 if equal * positive number if the this one is higher (other's is lower), 0 if equal
*/ */
comparePositionTo(role) { comparePositionTo(role) {
role = this.client.resolver.resolveRole(this.guild, role); role = this.guild.roles.resolve(role);
if (!role) return Promise.reject(new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake')); if (!role) return Promise.reject(new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake'));
return this.constructor.comparePositions(this, role); return this.constructor.comparePositions(this, role);
} }

View File

@@ -2,6 +2,7 @@ const GuildChannel = require('./GuildChannel');
const Webhook = require('./Webhook'); const Webhook = require('./Webhook');
const TextBasedChannel = require('./interfaces/TextBasedChannel'); const TextBasedChannel = require('./interfaces/TextBasedChannel');
const Collection = require('../util/Collection'); const Collection = require('../util/Collection');
const DataResolver = require('../util/DataResolver');
const MessageStore = require('../stores/MessageStore'); const MessageStore = require('../stores/MessageStore');
/** /**
@@ -63,7 +64,7 @@ class TextChannel extends GuildChannel {
*/ */
async createWebhook(name, { avatar, reason } = {}) { async createWebhook(name, { avatar, reason } = {}) {
if (typeof avatar === 'string' && !avatar.startsWith('data:')) { if (typeof avatar === 'string' && !avatar.startsWith('data:')) {
avatar = await this.client.resolver.resolveImage(avatar); avatar = await DataResolver.resolveImage(avatar, this.client.browser);
} }
return this.client.api.channels[this.id].webhooks.post({ data: { return this.client.api.channels[this.id].webhooks.post({ data: {
name, avatar, name, avatar,

View File

@@ -158,7 +158,7 @@ class User extends Base {
* @returns {boolean} * @returns {boolean}
*/ */
typingIn(channel) { typingIn(channel) {
channel = this.client.resolver.resolveChannel(channel); channel = this.client.channels.resolve(channel);
return channel._typing.has(this.id); return channel._typing.has(this.id);
} }
@@ -168,7 +168,7 @@ class User extends Base {
* @returns {?Date} * @returns {?Date}
*/ */
typingSinceIn(channel) { typingSinceIn(channel) {
channel = this.client.resolver.resolveChannel(channel); channel = this.client.channels.resolve(channel);
return channel._typing.has(this.id) ? new Date(channel._typing.get(this.id).since) : null; return channel._typing.has(this.id) ? new Date(channel._typing.get(this.id).since) : null;
} }
@@ -178,7 +178,7 @@ class User extends Base {
* @returns {number} * @returns {number}
*/ */
typingDurationIn(channel) { typingDurationIn(channel) {
channel = this.client.resolver.resolveChannel(channel); channel = this.client.channels.resolve(channel);
return channel._typing.has(this.id) ? channel._typing.get(this.id).elapsedTime : -1; return channel._typing.has(this.id) ? channel._typing.get(this.id).elapsedTime : -1;
} }

View File

@@ -1,5 +1,6 @@
const path = require('path'); const path = require('path');
const Util = require('../util/Util'); const Util = require('../util/Util');
const DataResolver = require('../util/DataResolver');
const Embed = require('./MessageEmbed'); const Embed = require('./MessageEmbed');
const MessageAttachment = require('./MessageAttachment'); const MessageAttachment = require('./MessageAttachment');
const MessageEmbed = require('./MessageEmbed'); const MessageEmbed = require('./MessageEmbed');
@@ -168,7 +169,7 @@ class Webhook {
} }
return Promise.all(options.files.map(file => return Promise.all(options.files.map(file =>
this.client.resolver.resolveFile(file.attachment).then(resource => { DataResolver.resolveFile(file.attachment, this.client.browser).then(resource => {
file.file = resource; file.file = resource;
return file; return file;
}) })
@@ -248,7 +249,7 @@ class Webhook {
*/ */
edit({ name = this.name, avatar }, reason) { edit({ name = this.name, avatar }, reason) {
if (avatar && (typeof avatar === 'string' && !avatar.startsWith('data:'))) { if (avatar && (typeof avatar === 'string' && !avatar.startsWith('data:'))) {
return this.client.resolver.resolveImage(avatar).then(image => return DataResolver.resolveImage(avatar, this.client.browser).then(image =>
this.edit({ name, avatar: image }, reason) this.edit({ name, avatar: image }, reason)
); );
} }

View File

@@ -1,9 +1,9 @@
const path = require('path'); const path = require('path');
const MessageCollector = require('../MessageCollector'); const MessageCollector = require('../MessageCollector');
const Shared = require('../shared'); const Shared = require('../shared');
const MessageStore = require('../../stores/MessageStore');
const Snowflake = require('../../util/Snowflake'); const Snowflake = require('../../util/Snowflake');
const Collection = require('../../util/Collection'); const Collection = require('../../util/Collection');
const DataResolver = require('../../util/DataResolver');
const MessageAttachment = require('../../structures/MessageAttachment'); const MessageAttachment = require('../../structures/MessageAttachment');
const MessageEmbed = require('../../structures/MessageEmbed'); const MessageEmbed = require('../../structures/MessageEmbed');
const { RangeError, TypeError } = require('../../errors'); const { RangeError, TypeError } = require('../../errors');
@@ -124,7 +124,7 @@ class TextBasedChannel {
} }
return Promise.all(options.files.map(file => return Promise.all(options.files.map(file =>
this.client.resolver.resolveFile(file.attachment).then(resource => { DataResolver.resolveFile(file.attachment, this.client.browser).then(resource => {
file.file = resource; file.file = resource;
return file; return file;
}) })
@@ -341,3 +341,6 @@ class TextBasedChannel {
} }
module.exports = TextBasedChannel; module.exports = TextBasedChannel;
// Fixes Circular
const MessageStore = require('../../stores/MessageStore');

View File

@@ -52,9 +52,9 @@ module.exports = function search(target, options) {
options.minID = long.fromNumber(t).shiftLeft(22).toString(); options.minID = long.fromNumber(t).shiftLeft(22).toString();
options.maxID = long.fromNumber(t + 864e5).shiftLeft(22).toString(); options.maxID = long.fromNumber(t + 864e5).shiftLeft(22).toString();
} }
if (options.channel) options.channel = target.client.resolver.resolveChannelID(options.channel); if (options.channel) options.channel = target.client.channels.resolveID(options.channel);
if (options.author) options.author = target.client.resolver.resolveUserID(options.author); if (options.author) options.author = target.client.users.resolveID(options.author);
if (options.mentions) options.mentions = target.client.resolver.resolveUserID(options.options.mentions); if (options.mentions) options.mentions = target.client.users.resolveID(options.options.mentions);
if (options.sortOrder) { if (options.sortOrder) {
options.sortOrder = { ascending: 'asc', descending: 'desc' }[options.sortOrder] || options.sortOrder; options.sortOrder = { ascending: 'asc', descending: 'desc' }[options.sortOrder] || options.sortOrder;
} }

View File

@@ -17,7 +17,7 @@ module.exports = function sendMessage(channel, options) { // eslint-disable-line
// Add the reply prefix // Add the reply prefix
if (reply && !(channel instanceof User || channel instanceof GuildMember) && channel.type !== 'dm') { if (reply && !(channel instanceof User || channel instanceof GuildMember) && channel.type !== 'dm') {
const id = channel.client.resolver.resolveUserID(reply); const id = channel.client.users.resolveID(reply);
const mention = `<@${reply instanceof GuildMember && reply.nickname ? '!' : ''}${id}>`; const mention = `<@${reply instanceof GuildMember && reply.nickname ? '!' : ''}${id}>`;
if (split) split.prepend = `${mention}, ${split.prepend || ''}`; if (split) split.prepend = `${mention}, ${split.prepend || ''}`;
content = `${mention}${typeof content !== 'undefined' ? `, ${content}` : ''}`; content = `${mention}${typeof content !== 'undefined' ? `, ${content}` : ''}`;

124
src/util/DataResolver.js Normal file
View File

@@ -0,0 +1,124 @@
const path = require('path');
const fs = require('fs');
const snekfetch = require('snekfetch');
const Util = require('../util/Util');
const { Error, TypeError } = require('../errors');
/**
* The DataResolver identifies different objects and tries to resolve a specific piece of information from them.
* @private
*/
class DataResolver {
constructor() {
throw new Error(`The ${this.constructor.name} class may not be instantiated.`);
}
/**
* Data that can be resolved to give an invite code. This can be:
* * An invite code
* * An invite URL
* @typedef {string} InviteResolvable
*/
/**
* Resolves InviteResolvable to an invite code.
* @param {InviteResolvable} data The invite resolvable to resolve
* @returns {string}
*/
static resolveInviteCode(data) {
const inviteRegex = /discord(?:app\.com\/invite|\.gg)\/([\w-]{2,255})/i;
const match = inviteRegex.exec(data);
if (match && match[1]) return match[1];
return data;
}
/**
* Resolves a Base64Resolvable, a string, or a BufferResolvable to a Base 64 image.
* @param {BufferResolvable|Base64Resolvable} image The image to be resolved
* @param {boolean} browser Whether this should resolve for a browser
* @returns {Promise<?string>}
*/
static async resolveImage(image, browser) {
if (!image) return null;
if (typeof image === 'string' && image.startsWith('data:')) {
return image;
}
const file = await this.resolveFile(image, browser);
return this.constructor.resolveBase64(file);
}
/**
* Data that resolves to give a Base64 string, typically for image uploading. This can be:
* * A Buffer
* * A base64 string
* @typedef {Buffer|string} Base64Resolvable
*/
/**
* Resolves a Base64Resolvable to a Base 64 image.
* @param {Base64Resolvable} data The base 64 resolvable you want to resolve
* @returns {?string}
*/
static resolveBase64(data) {
if (data instanceof Buffer) return `data:image/jpg;base64,${data.toString('base64')}`;
return data;
}
/**
* Data that can be resolved to give a Buffer. This can be:
* * A Buffer
* * The path to a local file
* * A URL
* @typedef {string|Buffer} BufferResolvable
*/
/**
* @external Stream
* @see {@link https://nodejs.org/api/stream.html}
*/
/**
* Resolves a BufferResolvable to a Buffer.
* @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve
* @param {boolean} browser Whether this should resolve for a browser
* @returns {Promise<Buffer>}
*/
static resolveFile(resource, browser) {
if (resource instanceof Buffer) return Promise.resolve(resource);
if (browser && resource instanceof ArrayBuffer) return Promise.resolve(Util.convertToBuffer(resource));
if (typeof resource === 'string') {
return new Promise((resolve, reject) => {
if (/^https?:\/\//.test(resource)) {
snekfetch.get(resource)
.end((err, res) => {
if (err) return reject(err);
if (!(res.body instanceof Buffer)) return reject(new TypeError('REQ_BODY_TYPE'));
return resolve(res.body);
});
} else {
const file = path.resolve(resource);
fs.stat(file, (err, stats) => {
if (err) return reject(err);
if (!stats || !stats.isFile()) return reject(new Error('FILE_NOT_FOUND', file));
fs.readFile(file, (err2, data) => {
if (err2) reject(err2); else resolve(data);
});
return null;
});
}
});
} else if (resource.pipe && typeof resource.pipe === 'function') {
return new Promise((resolve, reject) => {
const buffers = [];
resource.once('error', reject);
resource.on('data', data => buffers.push(data));
resource.once('end', () => resolve(Buffer.concat(buffers)));
});
}
return Promise.reject(new TypeError('REQ_RESOURCE_TYPE'));
}
}
module.exports = DataResolver;