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 RESTManager = require('../rest/RESTManager');
const ClientDataResolver = require('./ClientDataResolver');
const Util = require('../util/Util');
const Constants = require('../util/Constants');
@@ -25,13 +24,6 @@ class BaseClient extends EventEmitter {
*/
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
* @type {Set<Timeout>}

View File

@@ -17,6 +17,7 @@ const ChannelStore = require('../stores/ChannelStore');
const GuildStore = require('../stores/GuildStore');
const ClientPresenceStore = require('../stores/ClientPresenceStore');
const Constants = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const { Error, TypeError, RangeError } = require('../errors');
/**
@@ -292,7 +293,7 @@ class Client extends BaseClient {
* @returns {Promise<Invite>}
*/
fetchInvite(invite) {
const code = this.resolver.resolveInviteCode(invite);
const code = DataResolver.resolveInviteCode(invite);
return this.api.invites(code).get({ query: { with_counts: true } })
.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}
*/
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 (this.opusStreams.get(user.id)) throw new Error('VOICE_STREAM_EXISTS');
const stream = new Readable();
@@ -138,7 +138,7 @@ class VoiceReceiver extends EventEmitter {
* @returns {ReadableStream}
*/
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 (this.pcmStreams.get(user.id)) throw new Error('VOICE_STREAM_EXISTS');
const stream = new Readable();

View File

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

View File

@@ -16,7 +16,7 @@ class ChannelStore extends DataStore {
options = iterableOrOptions;
iterableOrOptions = undefined;
}
super(client, iterableOrOptions);
super(client, iterableOrOptions, Channel);
if (options.lru) {
const lru = this[kLru] = [];
@@ -72,6 +72,29 @@ class ChannelStore extends DataStore {
if (channel.guild) channel.guild.channels.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;

View File

@@ -5,15 +5,45 @@ const Collection = require('../util/Collection');
* @extends {Collection}
*/
class DataStore extends Collection {
constructor(client, iterable) {
constructor(client, iterable, holds) {
super();
Object.defineProperty(this, 'client', { value: client });
Object.defineProperty(this, 'holds', { value: holds });
if (iterable) for (const item of iterable) this.create(item);
}
// Stubs
create() { return undefined; }
create(data, cache = true, { id, extras = [] } = {}) {
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); }
/**
* 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;

View File

@@ -1,5 +1,7 @@
const DataStore = require('./DataStore');
const Emoji = require('../structures/Emoji');
const ReactionEmoji = require('../structures/ReactionEmoji');
/**
* Stores emojis.
* @private
@@ -7,20 +9,62 @@ const Emoji = require('../structures/Emoji');
*/
class EmojiStore extends DataStore {
constructor(guild, iterable) {
super(guild.client, iterable);
super(guild.client, iterable, Emoji);
this.guild = guild;
}
create(data) {
const guild = this.guild;
create(data, cache) {
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 Channel = require('../structures/Channel');
const GuildChannel = require('../structures/GuildChannel');
/**
* Stores guild channels.
@@ -8,7 +9,7 @@ const Channel = require('../structures/Channel');
*/
class GuildChannelStore extends DataStore {
constructor(guild, iterable) {
super(guild.client, iterable);
super(guild.client, iterable, GuildChannel);
this.guild = guild;
}
@@ -18,6 +19,29 @@ class GuildChannelStore extends DataStore {
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;

View File

@@ -10,18 +10,44 @@ const { Error } = require('../errors');
*/
class GuildMemberStore extends DataStore {
constructor(guild, iterable) {
super(guild.client, iterable);
super(guild.client, iterable, GuildMember);
this.guild = guild;
}
create(data, cache = true) {
const existing = this.get(data.user.id);
if (existing) return existing;
create(data, cache) {
return super.create(data, cache, { extras: [this.guild] });
}
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) {
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 (options.user) {
options.user = this.client.resolver.resolveUserID(options.user);
options.user = this.resolveID(options.user);
if (options.user) return this._fetchSingle(options);
}
return this._fetchMany(options);

View File

@@ -1,20 +1,38 @@
const DataStore = require('./DataStore');
const Guild = require('../structures/Guild');
/**
* Stores guilds.
* @private
* @extends {DataStore}
*/
class GuildStore extends DataStore {
create(data, cache = true) {
const existing = this.get(data.id);
if (existing) return existing;
const guild = new Guild(this.client, data);
if (cache) this.set(guild.id, guild);
return guild;
constructor(client, iterable) {
super(client, iterable, 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;

View File

@@ -1,7 +1,7 @@
const DataStore = require('./DataStore');
const Collection = require('../util/Collection');
const Message = require('../structures/Message');
const { Error } = require('../errors');
let Message;
/**
* Stores messages for text-based channels.
@@ -9,19 +9,12 @@ let Message;
*/
class MessageStore extends DataStore {
constructor(channel, iterable) {
super(channel.client, iterable);
super(channel.client, iterable, Message);
this.channel = channel;
Message = require('../structures/Message');
}
create(data, cache = true) {
const existing = this.get(data.id);
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;
create(data, cache) {
return super.create(data, cache, { extras: [this.channel] });
}
set(key, value) {
@@ -62,7 +55,7 @@ class MessageStore extends DataStore {
/**
* 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>
* @returns {Promise<Collection<Snowflake, Message>>}
*/
@@ -95,6 +88,30 @@ class MessageStore extends DataStore {
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;

View File

@@ -2,13 +2,46 @@ const DataStore = require('./DataStore');
const { Presence } = require('../structures/Presence');
class PresenceStore extends DataStore {
create(data) {
if (this.has(data.user.id)) {
this.get(data.user.id).patch(data);
} else {
this.set(data.user.id, new Presence(this.client, data));
}
return this.get(data.user.id);
constructor(client, iterable) {
super(client, iterable, Presence);
}
create(data, cache) {
const existing = 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 MessageReaction = require('../structures/MessageReaction');
/**
* Stores reactions.
* @private
@@ -7,21 +8,37 @@ const MessageReaction = require('../structures/MessageReaction');
*/
class ReactionStore extends DataStore {
constructor(message, iterable) {
super(message.client, iterable);
super(message.client, iterable, MessageReaction);
this.message = message;
}
create(data) {
const emojiID = data.emoji.id || decodeURIComponent(data.emoji.name);
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;
create(data, cache) {
data.emoji.id = data.emoji.id || decodeURIComponent(data.emoji.name);
return super.create(data, cache, { id: data.emoji.id, extras: [this.message] });
}
/**
* 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;

View File

@@ -1,5 +1,6 @@
const DataStore = require('./DataStore');
const Role = require('../structures/Role');
/**
* Stores roles.
* @private
@@ -7,19 +8,36 @@ const Role = require('../structures/Role');
*/
class RoleStore extends DataStore {
constructor(guild, iterable) {
super(guild.client, iterable);
super(guild.client, iterable, Role);
this.guild = guild;
}
create(data) {
const existing = this.get(data.id);
if (existing) return existing;
const role = new Role(this.guild, data);
this.set(role.id, role);
return role;
create(data, cache) {
return super.create(data, cache, { extras: [this.guild] });
}
/**
* 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;

View File

@@ -1,18 +1,46 @@
const DataStore = require('./DataStore');
const User = require('../structures/User');
const GuildMember = require('../structures/GuildMember');
const Message = require('../structures/Message');
/**
* A data store to store User models.
* @extends {DataStore}
*/
class UserStore extends DataStore {
create(data, cache = true) {
const existing = this.get(data.id);
if (existing) return existing;
constructor(client, iterable) {
super(client, iterable, User);
}
const user = new User(this.client, data);
if (cache) this.set(user.id, user);
return user;
/**
* 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}
*/
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 Constants = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const Base = require('./Base');
/**
@@ -165,7 +166,7 @@ class ClientApplication extends Base {
* @returns {Promise}
*/
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: {
name,
data: b64,

View File

@@ -4,6 +4,7 @@ const ClientUserSettings = require('./ClientUserSettings');
const ClientUserGuildSettings = require('./ClientUserGuildSettings');
const Constants = require('../util/Constants');
const Util = require('../util/Util');
const DataResolver = require('../util/DataResolver');
const Guild = require('./Guild');
/**
@@ -177,7 +178,7 @@ class ClientUser extends User {
* .catch(console.error);
*/
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 }));
}
@@ -320,7 +321,7 @@ class ClientUser extends User {
if (r.nick) o[r.user ? r.user.id : r.id] = r.nick;
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 })
.then(res => this.client.channels.create(res));
}

View File

@@ -8,8 +8,8 @@ const { TypeError } = require('../errors');
* @extends {Base}
*/
class Emoji extends Base {
constructor(guild, data) {
super(guild.client);
constructor(client, data, guild) {
super(client);
/**
* The guild this emoji is part of
@@ -152,7 +152,7 @@ class Emoji extends Base {
addRestrictedRoles(roles) {
const newRoles = new Collection(this.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) {
return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
'Array or Collection of Roles or Snowflakes', true));
@@ -179,7 +179,7 @@ class Emoji extends Base {
removeRestrictedRoles(roles) {
const newRoles = new Collection(this.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) {
return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
'Array or Collection of Roles or Snowflakes', true));

View File

@@ -1,6 +1,7 @@
const Channel = require('./Channel');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const Collection = require('../util/Collection');
const DataResolver = require('../util/DataResolver');
const MessageStore = require('../stores/MessageStore');
/*
@@ -160,7 +161,7 @@ class GroupDMChannel extends Channel {
* @returns {Promise<GroupDMChannel>}
*/
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>}
*/
addUser({ user, accessToken, nick }) {
const id = this.client.resolver.resolveUserID(user);
const id = this.client.users.resolveID(user);
const data = this.client.user.bot ?
{ nick, access_token: accessToken } :
{ recipient: id };
@@ -196,7 +197,7 @@ class GroupDMChannel extends Channel {
* @returns {Promise<GroupDMChannel>}
*/
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()
.then(() => this);
}

View File

@@ -9,6 +9,7 @@ const VoiceRegion = require('./VoiceRegion');
const Constants = require('../util/Constants');
const Collection = require('../util/Collection');
const Util = require('../util/Util');
const DataResolver = require('../util/DataResolver');
const Snowflake = require('../util/Snowflake');
const Permissions = require('../util/Permissions');
const Shared = require('./shared');
@@ -431,7 +432,7 @@ class Guild extends Base {
* const member = guild.member(message.author);
*/
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,
after: options.after,
limit: options.limit,
user_id: this.client.resolver.resolveUserID(options.user),
user_id: this.client.users.resolveID(options.user),
action_type: options.type,
} })
.then(data => GuildAuditLogs.build(this, data));
@@ -536,7 +537,7 @@ class Guild extends Base {
if (options.roles) {
const 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) {
return Promise.reject(new TypeError('INVALID_TYPE', 'options.roles',
'Array or Collection of Roles or Snowflakes', true));
@@ -601,14 +602,14 @@ class Guild extends Base {
if (data.region) _data.region = data.region;
if (typeof data.verificationLevel !== 'undefined') _data.verification_level = Number(data.verificationLevel);
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') {
_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 (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 (typeof data.explicitContentFilter !== 'undefined') {
_data.explicit_content_filter = Number(data.explicitContentFilter);
@@ -724,7 +725,7 @@ class Guild extends Base {
* .catch(console.error);
*/
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);
*/
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 }) {
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));
return this.client.api.guilds(this.id).bans[id].put({ query: options })
.then(() => {
if (user instanceof GuildMember) return user;
const _user = this.client.resolver.resolveUser(id);
const _user = this.client.users.resolve(id);
if (_user) {
const member = this.client.resolver.resolveGuildMember(this, _user);
const member = this.members.resolve(_user);
return member || _user;
}
return id;
@@ -841,7 +842,7 @@ class Guild extends Base {
* .catch(console.error);
*/
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');
return this.client.api.guilds(this.id).bans[id].delete({ reason })
.then(() => user);
@@ -910,12 +911,12 @@ class Guild extends Base {
if (allow instanceof Array) allow = Permissions.resolve(allow);
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) {
overwrite.id = role.id;
overwrite.type = 'role';
} else {
overwrite.id = this.client.resolver.resolveUserID(overwrite.id);
overwrite.id = this.client.users.resolveID(overwrite.id);
overwrite.type = 'member';
}
@@ -988,7 +989,7 @@ class Guild extends Base {
*/
setChannelPositions(channelPositions) {
const updatedChannels = channelPositions.map(r => ({
id: this.client.resolver.resolveChannelID(r.channel),
id: this.client.channels.resolveID(r.channel),
position: r.position,
}));
@@ -1063,7 +1064,7 @@ class Guild extends Base {
if (roles) {
data.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) {
return Promise.reject(new TypeError('INVALID_TYPE', 'options.roles',
'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);
}
return this.client.resolver.resolveImage(attachment)
return DataResolver.resolveImage(attachment, this.client.browser)
.then(image => this.createEmoji(image, name, { roles, reason }));
}

View File

@@ -66,7 +66,7 @@ class GuildChannel extends Channel {
* @returns {?Permissions}
*/
permissionsFor(member) {
member = this.client.resolver.resolveGuildMember(this.guild, member);
member = this.guild.members.resolve(member);
if (!member) return null;
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) {
if (!verified) member = this.client.resolver.resolveGuildMember(this.guild, member);
if (!verified) member = this.guild.members.resolve(member);
if (!member) return [];
roles = roles || member.roles;
@@ -158,13 +158,13 @@ class GuildChannel extends Channel {
deny: 0,
};
if (userOrRole instanceof Role) {
payload.type = 'role';
} else if (this.guild.roles.has(userOrRole)) {
userOrRole = this.guild.roles.get(userOrRole);
const role = this.guild.roles.get(userOrRole);
if (role || userOrRole instanceof Role) {
userOrRole = role || userOrRole;
payload.type = 'role';
} else {
userOrRole = this.client.resolver.resolveUser(userOrRole);
userOrRole = this.client.users.resolve(userOrRole);
payload.type = 'member';
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}
*/
class GuildMember extends Base {
constructor(guild, data) {
super(guild.client);
constructor(client, data, guild) {
super(client);
/**
* The guild that this member is part of
@@ -291,7 +291,7 @@ class GuildMember extends Base {
* @returns {?Permissions}
*/
permissionsIn(channel) {
channel = this.client.resolver.resolveChannel(channel);
channel = this.client.channels.resolve(channel);
if (!channel || !channel.guild) throw new Error('GUILD_CHANNEL_RESOLVE');
return channel.permissionsFor(this);
}
@@ -342,7 +342,7 @@ class GuildMember extends Base {
*/
edit(data, reason) {
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;
}
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>}
*/
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 (this._roles.includes(role.id)) return Promise.resolve(this);
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) {
let allRoles = this._roles.slice();
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) {
return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
'Array or Collection of Roles or Snowflakes', true));
@@ -450,7 +450,7 @@ class GuildMember extends Base {
* @returns {Promise<GuildMember>}
*/
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 (!this._roles.includes(role.id)) return Promise.resolve(this);
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) {
const allRoles = this._roles.slice();
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) {
return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
'Array or Collection of Roles or Snowflakes', true));

View File

@@ -17,7 +17,7 @@ let GuildMember;
* @extends {Base}
*/
class Message extends Base {
constructor(channel, data, client) {
constructor(client, data, channel) {
super(client);
/**
@@ -393,7 +393,7 @@ class Message extends Base {
// Add the reply prefix
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}>`;
content = `${mention}${content ? `, ${content}` : ''}`;
}
@@ -427,11 +427,11 @@ class Message extends Base {
/**
* 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>}
*/
react(emoji) {
emoji = this.client.resolver.resolveEmojiIdentifier(emoji);
emoji = this.client.emojis.resolveIdentifier(emoji);
if (!emoji) throw new TypeError('EMOJI_TYPE');
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.
*/
class MessageReaction {
constructor(message, emoji, count, me) {
constructor(client, data, message) {
/**
* The message that this reaction refers to
* @type {Message}
@@ -18,13 +18,13 @@ class MessageReaction {
* Whether the client has given this reaction
* @type {boolean}
*/
this.me = me;
this.me = data.me;
/**
* The number of people that have given the same reaction
* @type {number}
*/
this.count = count || 0;
this.count = data.count || 0;
/**
* The users that have given this reaction, mapped by their ID
@@ -32,7 +32,7 @@ class MessageReaction {
*/
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>}
*/
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'));
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]

View File

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

View File

@@ -9,8 +9,8 @@ const { TypeError } = require('../errors');
* @extends {Base}
*/
class Role extends Base {
constructor(guild, data) {
super(guild.client);
constructor(client, data, guild) {
super(client);
/**
* 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
*/
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'));
return this.constructor.comparePositions(this, role);
}

View File

@@ -2,6 +2,7 @@ const GuildChannel = require('./GuildChannel');
const Webhook = require('./Webhook');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const Collection = require('../util/Collection');
const DataResolver = require('../util/DataResolver');
const MessageStore = require('../stores/MessageStore');
/**
@@ -63,7 +64,7 @@ class TextChannel extends GuildChannel {
*/
async createWebhook(name, { avatar, reason } = {}) {
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: {
name, avatar,

View File

@@ -158,7 +158,7 @@ class User extends Base {
* @returns {boolean}
*/
typingIn(channel) {
channel = this.client.resolver.resolveChannel(channel);
channel = this.client.channels.resolve(channel);
return channel._typing.has(this.id);
}
@@ -168,7 +168,7 @@ class User extends Base {
* @returns {?Date}
*/
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;
}
@@ -178,7 +178,7 @@ class User extends Base {
* @returns {number}
*/
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;
}

View File

@@ -1,5 +1,6 @@
const path = require('path');
const Util = require('../util/Util');
const DataResolver = require('../util/DataResolver');
const Embed = require('./MessageEmbed');
const MessageAttachment = require('./MessageAttachment');
const MessageEmbed = require('./MessageEmbed');
@@ -168,7 +169,7 @@ class Webhook {
}
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;
return file;
})
@@ -248,7 +249,7 @@ class Webhook {
*/
edit({ name = this.name, avatar }, reason) {
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)
);
}

View File

@@ -1,9 +1,9 @@
const path = require('path');
const MessageCollector = require('../MessageCollector');
const Shared = require('../shared');
const MessageStore = require('../../stores/MessageStore');
const Snowflake = require('../../util/Snowflake');
const Collection = require('../../util/Collection');
const DataResolver = require('../../util/DataResolver');
const MessageAttachment = require('../../structures/MessageAttachment');
const MessageEmbed = require('../../structures/MessageEmbed');
const { RangeError, TypeError } = require('../../errors');
@@ -124,7 +124,7 @@ class TextBasedChannel {
}
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;
return file;
})
@@ -341,3 +341,6 @@ class 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.maxID = long.fromNumber(t + 864e5).shiftLeft(22).toString();
}
if (options.channel) options.channel = target.client.resolver.resolveChannelID(options.channel);
if (options.author) options.author = target.client.resolver.resolveUserID(options.author);
if (options.mentions) options.mentions = target.client.resolver.resolveUserID(options.options.mentions);
if (options.channel) options.channel = target.client.channels.resolveID(options.channel);
if (options.author) options.author = target.client.users.resolveID(options.author);
if (options.mentions) options.mentions = target.client.users.resolveID(options.options.mentions);
if (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
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}>`;
if (split) split.prepend = `${mention}, ${split.prepend || ''}`;
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;