aboutsummaryrefslogtreecommitdiff
path: root/node_modules/discord.js/src/structures
diff options
context:
space:
mode:
authorAlee14 <alee14498@gmail.com>2017-03-26 15:18:10 -0400
committerAlee14 <alee14498@gmail.com>2017-03-26 15:18:10 -0400
commit29433e2f7dbd0e4a73d3c78ffe1005b922fb5982 (patch)
treeaa0ad3fe59468cbe452ee597e914839b68c01436 /node_modules/discord.js/src/structures
parent878fefb4c4e1f12b804ae5c0def433fa873f4c8b (diff)
downloadAleeBot-29433e2f7dbd0e4a73d3c78ffe1005b922fb5982.tar.gz
AleeBot-29433e2f7dbd0e4a73d3c78ffe1005b922fb5982.tar.bz2
AleeBot-29433e2f7dbd0e4a73d3c78ffe1005b922fb5982.zip
Don't mind me i'm adding the discord.js files
Diffstat (limited to 'node_modules/discord.js/src/structures')
-rw-r--r--node_modules/discord.js/src/structures/Channel.js67
-rw-r--r--node_modules/discord.js/src/structures/ClientOAuth2Application.js26
-rw-r--r--node_modules/discord.js/src/structures/ClientUser.js274
-rw-r--r--node_modules/discord.js/src/structures/DMChannel.js60
-rw-r--r--node_modules/discord.js/src/structures/Emoji.js140
-rw-r--r--node_modules/discord.js/src/structures/EvaluatedPermissions.js67
-rw-r--r--node_modules/discord.js/src/structures/GroupDMChannel.js144
-rw-r--r--node_modules/discord.js/src/structures/Guild.js851
-rw-r--r--node_modules/discord.js/src/structures/GuildChannel.js299
-rw-r--r--node_modules/discord.js/src/structures/GuildMember.js442
-rw-r--r--node_modules/discord.js/src/structures/Invite.js159
-rw-r--r--node_modules/discord.js/src/structures/Message.js568
-rw-r--r--node_modules/discord.js/src/structures/MessageAttachment.js68
-rw-r--r--node_modules/discord.js/src/structures/MessageCollector.js151
-rw-r--r--node_modules/discord.js/src/structures/MessageEmbed.js293
-rw-r--r--node_modules/discord.js/src/structures/MessageReaction.js92
-rw-r--r--node_modules/discord.js/src/structures/OAuth2Application.js82
-rw-r--r--node_modules/discord.js/src/structures/PartialGuild.js51
-rw-r--r--node_modules/discord.js/src/structures/PartialGuildChannel.js44
-rw-r--r--node_modules/discord.js/src/structures/PermissionOverwrites.js43
-rw-r--r--node_modules/discord.js/src/structures/Presence.js92
-rw-r--r--node_modules/discord.js/src/structures/ReactionEmoji.js49
-rw-r--r--node_modules/discord.js/src/structures/RichEmbed.js204
-rw-r--r--node_modules/discord.js/src/structures/Role.js341
-rw-r--r--node_modules/discord.js/src/structures/TextChannel.js96
-rw-r--r--node_modules/discord.js/src/structures/User.js277
-rw-r--r--node_modules/discord.js/src/structures/UserConnection.js48
-rw-r--r--node_modules/discord.js/src/structures/UserProfile.js56
-rw-r--r--node_modules/discord.js/src/structures/VoiceChannel.js120
-rw-r--r--node_modules/discord.js/src/structures/Webhook.js200
-rw-r--r--node_modules/discord.js/src/structures/interface/TextBasedChannel.js377
31 files changed, 5781 insertions, 0 deletions
diff --git a/node_modules/discord.js/src/structures/Channel.js b/node_modules/discord.js/src/structures/Channel.js
new file mode 100644
index 0000000..b37b14b
--- /dev/null
+++ b/node_modules/discord.js/src/structures/Channel.js
@@ -0,0 +1,67 @@
+/**
+ * Represents any channel on Discord
+ */
+class Channel {
+ constructor(client, data) {
+ /**
+ * The client that instantiated the Channel
+ * @name Channel#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: client });
+
+ /**
+ * The type of the channel, either:
+ * * `dm` - a DM channel
+ * * `group` - a Group DM channel
+ * * `text` - a guild text channel
+ * * `voice` - a guild voice channel
+ * @type {string}
+ */
+ this.type = null;
+
+ if (data) this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The unique ID of the channel
+ * @type {string}
+ */
+ this.id = data.id;
+ }
+
+ /**
+ * The timestamp the channel was created at
+ * @type {number}
+ * @readonly
+ */
+ get createdTimestamp() {
+ return (this.id / 4194304) + 1420070400000;
+ }
+
+ /**
+ * The time the channel was created
+ * @type {Date}
+ * @readonly
+ */
+ get createdAt() {
+ return new Date(this.createdTimestamp);
+ }
+
+ /**
+ * Deletes the channel
+ * @returns {Promise<Channel>}
+ * @example
+ * // delete the channel
+ * channel.delete()
+ * .then() // success
+ * .catch(console.error); // log error
+ */
+ delete() {
+ return this.client.rest.methods.deleteChannel(this);
+ }
+}
+
+module.exports = Channel;
diff --git a/node_modules/discord.js/src/structures/ClientOAuth2Application.js b/node_modules/discord.js/src/structures/ClientOAuth2Application.js
new file mode 100644
index 0000000..46e1250
--- /dev/null
+++ b/node_modules/discord.js/src/structures/ClientOAuth2Application.js
@@ -0,0 +1,26 @@
+const User = require('./User');
+const OAuth2Application = require('./OAuth2Application');
+
+/**
+ * Represents the client's OAuth2 Application
+ * @extends {OAuth2Application}
+ */
+class ClientOAuth2Application extends OAuth2Application {
+ setup(data) {
+ super.setup(data);
+
+ /**
+ * The app's flags
+ * @type {number}
+ */
+ this.flags = data.flags;
+
+ /**
+ * The app's owner
+ * @type {User}
+ */
+ this.owner = new User(this.client, data.owner);
+ }
+}
+
+module.exports = ClientOAuth2Application;
diff --git a/node_modules/discord.js/src/structures/ClientUser.js b/node_modules/discord.js/src/structures/ClientUser.js
new file mode 100644
index 0000000..d526af6
--- /dev/null
+++ b/node_modules/discord.js/src/structures/ClientUser.js
@@ -0,0 +1,274 @@
+const User = require('./User');
+const Collection = require('../util/Collection');
+
+/**
+ * Represents the logged in client's Discord user
+ * @extends {User}
+ */
+class ClientUser extends User {
+ setup(data) {
+ super.setup(data);
+
+ /**
+ * Whether or not this account has been verified
+ * @type {boolean}
+ */
+ this.verified = data.verified;
+
+ /**
+ * The email of this account
+ * @type {string}
+ */
+ this.email = data.email;
+ this.localPresence = {};
+ this._typing = new Map();
+
+ /**
+ * A Collection of friends for the logged in user.
+ * <warn>This is only filled when using a user account.</warn>
+ * @type {Collection<string, User>}
+ */
+ this.friends = new Collection();
+
+ /**
+ * A Collection of blocked users for the logged in user.
+ * <warn>This is only filled when using a user account.</warn>
+ * @type {Collection<string, User>}
+ */
+ this.blocked = new Collection();
+
+ /**
+ * A Collection of notes for the logged in user.
+ * <warn>This is only filled when using a user account.</warn>
+ * @type {Collection<string, string>}
+ */
+ this.notes = new Collection();
+ }
+
+ edit(data) {
+ return this.client.rest.methods.updateCurrentUser(data);
+ }
+
+ /**
+ * Set the username of the logged in Client.
+ * <info>Changing usernames in Discord is heavily rate limited, with only 2 requests
+ * every hour. Use this sparingly!</info>
+ * @param {string} username The new username
+ * @param {string} [password] Current password (only for user accounts)
+ * @returns {Promise<ClientUser>}
+ * @example
+ * // set username
+ * client.user.setUsername('discordjs')
+ * .then(user => console.log(`My new username is ${user.username}`))
+ * .catch(console.error);
+ */
+ setUsername(username, password) {
+ return this.client.rest.methods.updateCurrentUser({ username }, password);
+ }
+
+ /**
+ * Changes the email for the client user's account.
+ * <warn>This is only available when using a user account.</warn>
+ * @param {string} email New email to change to
+ * @param {string} password Current password
+ * @returns {Promise<ClientUser>}
+ * @example
+ * // set email
+ * client.user.setEmail('bob@gmail.com', 'some amazing password 123')
+ * .then(user => console.log(`My new email is ${user.email}`))
+ * .catch(console.error);
+ */
+ setEmail(email, password) {
+ return this.client.rest.methods.updateCurrentUser({ email }, password);
+ }
+
+ /**
+ * Changes the password for the client user's account.
+ * <warn>This is only available when using a user account.</warn>
+ * @param {string} newPassword New password to change to
+ * @param {string} oldPassword Current password
+ * @returns {Promise<ClientUser>}
+ * @example
+ * // set password
+ * client.user.setPassword('some new amazing password 456', 'some amazing password 123')
+ * .then(user => console.log('New password set!'))
+ * .catch(console.error);
+ */
+ setPassword(newPassword, oldPassword) {
+ return this.client.rest.methods.updateCurrentUser({ password: newPassword }, oldPassword);
+ }
+
+ /**
+ * Set the avatar of the logged in Client.
+ * @param {BufferResolvable|Base64Resolvable} avatar The new avatar
+ * @returns {Promise<ClientUser>}
+ * @example
+ * // set avatar
+ * client.user.setAvatar('./avatar.png')
+ * .then(user => console.log(`New avatar set!`))
+ * .catch(console.error);
+ */
+ setAvatar(avatar) {
+ if (avatar.startsWith('data:')) {
+ return this.client.rest.methods.updateCurrentUser({ avatar });
+ } else {
+ return this.client.resolver.resolveBuffer(avatar).then(data =>
+ this.client.rest.methods.updateCurrentUser({ avatar: data })
+ );
+ }
+ }
+
+ /**
+ * Data resembling a raw Discord presence
+ * @typedef {Object} PresenceData
+ * @property {PresenceStatus} [status] Status of the user
+ * @property {boolean} [afk] Whether the user is AFK
+ * @property {Object} [game] Game the user is playing
+ * @property {string} [game.name] Name of the game
+ * @property {string} [game.url] Twitch stream URL
+ */
+
+ /**
+ * Sets the full presence of the client user.
+ * @param {PresenceData} data Data for the presence
+ * @returns {Promise<ClientUser>}
+ */
+ setPresence(data) {
+ // {"op":3,"d":{"status":"dnd","since":0,"game":null,"afk":false}}
+ return new Promise(resolve => {
+ let status = this.localPresence.status || this.presence.status;
+ let game = this.localPresence.game;
+ let afk = this.localPresence.afk || this.presence.afk;
+
+ if (!game && this.presence.game) {
+ game = {
+ name: this.presence.game.name,
+ type: this.presence.game.type,
+ url: this.presence.game.url,
+ };
+ }
+
+ if (data.status) {
+ if (typeof data.status !== 'string') throw new TypeError('Status must be a string');
+ status = data.status;
+ }
+
+ if (data.game) {
+ game = data.game;
+ if (game.url) game.type = 1;
+ }
+
+ if (typeof data.afk !== 'undefined') afk = data.afk;
+ afk = Boolean(afk);
+
+ this.localPresence = { status, game, afk };
+ this.localPresence.since = 0;
+ this.localPresence.game = this.localPresence.game || null;
+
+ this.client.ws.send({
+ op: 3,
+ d: this.localPresence,
+ });
+
+ this.client._setPresence(this.id, this.localPresence);
+
+ resolve(this);
+ });
+ }
+
+ /**
+ * A user's status. Must be one of:
+ * - `online`
+ * - `idle`
+ * - `invisible`
+ * - `dnd` (do not disturb)
+ * @typedef {string} PresenceStatus
+ */
+
+ /**
+ * Sets the status of the client user.
+ * @param {PresenceStatus} status Status to change to
+ * @returns {Promise<ClientUser>}
+ */
+ setStatus(status) {
+ return this.setPresence({ status });
+ }
+
+ /**
+ * Sets the game the client user is playing.
+ * @param {string} game Game being played
+ * @param {string} [streamingURL] Twitch stream URL
+ * @returns {Promise<ClientUser>}
+ */
+ setGame(game, streamingURL) {
+ return this.setPresence({ game: {
+ name: game,
+ url: streamingURL,
+ } });
+ }
+
+ /**
+ * Sets/removes the AFK flag for the client user.
+ * @param {boolean} afk Whether or not the user is AFK
+ * @returns {Promise<ClientUser>}
+ */
+ setAFK(afk) {
+ return this.setPresence({ afk });
+ }
+
+ /**
+ * Fetches messages that mentioned the client's user
+ * @param {Object} [options] Options for the fetch
+ * @param {number} [options.limit=25] Maximum number of mentions to retrieve
+ * @param {boolean} [options.roles=true] Whether to include role mentions
+ * @param {boolean} [options.everyone=true] Whether to include everyone/here mentions
+ * @param {Guild|string} [options.guild] Limit the search to a specific guild
+ * @returns {Promise<Message[]>}
+ */
+ fetchMentions(options = { limit: 25, roles: true, everyone: true, guild: null }) {
+ return this.client.rest.methods.fetchMentions(options);
+ }
+
+ /**
+ * Send a friend request
+ * <warn>This is only available when using a user account.</warn>
+ * @param {UserResolvable} user The user to send the friend request to.
+ * @returns {Promise<User>} The user the friend request was sent to.
+ */
+ addFriend(user) {
+ user = this.client.resolver.resolveUser(user);
+ return this.client.rest.methods.addFriend(user);
+ }
+
+ /**
+ * Remove a friend
+ * <warn>This is only available when using a user account.</warn>
+ * @param {UserResolvable} user The user to remove from your friends
+ * @returns {Promise<User>} The user that was removed
+ */
+ removeFriend(user) {
+ user = this.client.resolver.resolveUser(user);
+ return this.client.rest.methods.removeFriend(user);
+ }
+
+ /**
+ * Creates a guild
+ * <warn>This is only available when using a user account.</warn>
+ * @param {string} name The name of the guild
+ * @param {string} region The region for the server
+ * @param {BufferResolvable|Base64Resolvable} [icon=null] The icon for the guild
+ * @returns {Promise<Guild>} The guild that was created
+ */
+ createGuild(name, region, icon = null) {
+ if (!icon) return this.client.rest.methods.createGuild({ name, icon, region });
+ if (icon.startsWith('data:')) {
+ return this.client.rest.methods.createGuild({ name, icon, region });
+ } else {
+ return this.client.resolver.resolveBuffer(icon).then(data =>
+ this.client.rest.methods.createGuild({ name, icon: data, region })
+ );
+ }
+ }
+}
+
+module.exports = ClientUser;
diff --git a/node_modules/discord.js/src/structures/DMChannel.js b/node_modules/discord.js/src/structures/DMChannel.js
new file mode 100644
index 0000000..de49c8b
--- /dev/null
+++ b/node_modules/discord.js/src/structures/DMChannel.js
@@ -0,0 +1,60 @@
+const Channel = require('./Channel');
+const TextBasedChannel = require('./interface/TextBasedChannel');
+const Collection = require('../util/Collection');
+
+/**
+ * Represents a direct message channel between two users.
+ * @extends {Channel}
+ * @implements {TextBasedChannel}
+ */
+class DMChannel extends Channel {
+ constructor(client, data) {
+ super(client, data);
+ this.type = 'dm';
+ this.messages = new Collection();
+ this._typing = new Map();
+ }
+
+ setup(data) {
+ super.setup(data);
+
+ /**
+ * The recipient on the other end of the DM
+ * @type {User}
+ */
+ this.recipient = this.client.dataManager.newUser(data.recipients[0]);
+
+ this.lastMessageID = data.last_message_id;
+ }
+
+ /**
+ * When concatenated with a string, this automatically concatenates the recipient's mention instead of the
+ * DM channel object.
+ * @returns {string}
+ */
+ toString() {
+ return this.recipient.toString();
+ }
+
+ // These are here only for documentation purposes - they are implemented by TextBasedChannel
+ send() { return; }
+ sendMessage() { return; }
+ sendEmbed() { return; }
+ sendFile() { return; }
+ sendCode() { return; }
+ fetchMessage() { return; }
+ fetchMessages() { return; }
+ fetchPinnedMessages() { return; }
+ startTyping() { return; }
+ stopTyping() { return; }
+ get typing() { return; }
+ get typingCount() { return; }
+ createCollector() { return; }
+ awaitMessages() { return; }
+ bulkDelete() { return; }
+ _cacheMessage() { return; }
+}
+
+TextBasedChannel.applyToClass(DMChannel, true);
+
+module.exports = DMChannel;
diff --git a/node_modules/discord.js/src/structures/Emoji.js b/node_modules/discord.js/src/structures/Emoji.js
new file mode 100644
index 0000000..d8a62e1
--- /dev/null
+++ b/node_modules/discord.js/src/structures/Emoji.js
@@ -0,0 +1,140 @@
+const Constants = require('../util/Constants');
+const Collection = require('../util/Collection');
+
+/**
+ * Represents a custom emoji
+ */
+class Emoji {
+ constructor(guild, data) {
+ /**
+ * The Client that instantiated this object
+ * @name Emoji#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: guild.client });
+
+ /**
+ * The guild this emoji is part of
+ * @type {Guild}
+ */
+ this.guild = guild;
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The ID of the emoji
+ * @type {string}
+ */
+ this.id = data.id;
+
+ /**
+ * The name of the emoji
+ * @type {string}
+ */
+ this.name = data.name;
+
+ /**
+ * Whether or not this emoji requires colons surrounding it
+ * @type {boolean}
+ */
+ this.requiresColons = data.require_colons;
+
+ /**
+ * Whether this emoji is managed by an external service
+ * @type {boolean}
+ */
+ this.managed = data.managed;
+
+ this._roles = data.roles;
+ }
+
+ /**
+ * The timestamp the emoji was created at
+ * @type {number}
+ * @readonly
+ */
+ get createdTimestamp() {
+ return (this.id / 4194304) + 1420070400000;
+ }
+
+ /**
+ * The time the emoji was created
+ * @type {Date}
+ * @readonly
+ */
+ get createdAt() {
+ return new Date(this.createdTimestamp);
+ }
+
+ /**
+ * A collection of roles this emoji is active for (empty if all), mapped by role ID.
+ * @type {Collection<string, Role>}
+ * @readonly
+ */
+ get roles() {
+ const roles = new Collection();
+ for (const role of this._roles) {
+ if (this.guild.roles.has(role)) roles.set(role, this.guild.roles.get(role));
+ }
+ return roles;
+ }
+
+ /**
+ * The URL to the emoji file
+ * @type {string}
+ * @readonly
+ */
+ get url() {
+ return Constants.Endpoints.emoji(this.id);
+ }
+
+ /**
+ * When concatenated with a string, this automatically returns the emoji mention rather than the object.
+ * @returns {string}
+ * @example
+ * // send an emoji:
+ * const emoji = guild.emojis.first();
+ * msg.reply(`Hello! ${emoji}`);
+ */
+ toString() {
+ return this.requiresColons ? `<:${this.name}:${this.id}>` : this.name;
+ }
+
+ /**
+ * Whether this emoji is the same as another one
+ * @param {Emoji|Object} other the emoji to compare it to
+ * @returns {boolean} whether the emoji is equal to the given emoji or not
+ */
+ equals(other) {
+ if (other instanceof Emoji) {
+ return (
+ other.id === this.id &&
+ other.name === this.name &&
+ other.managed === this.managed &&
+ other.requiresColons === this.requiresColons
+ );
+ } else {
+ return (
+ other.id === this.id &&
+ other.name === this.name
+ );
+ }
+ }
+
+ /**
+ * The identifier of this emoji, used for message reactions
+ * @readonly
+ * @type {string}
+ */
+ get identifier() {
+ if (this.id) {
+ return `${this.name}:${this.id}`;
+ }
+ return encodeURIComponent(this.name);
+ }
+}
+
+module.exports = Emoji;
diff --git a/node_modules/discord.js/src/structures/EvaluatedPermissions.js b/node_modules/discord.js/src/structures/EvaluatedPermissions.js
new file mode 100644
index 0000000..ae8a643
--- /dev/null
+++ b/node_modules/discord.js/src/structures/EvaluatedPermissions.js
@@ -0,0 +1,67 @@
+const Constants = require('../util/Constants');
+
+/**
+ * The final evaluated permissions for a member in a channel
+ */
+class EvaluatedPermissions {
+ constructor(member, raw) {
+ /**
+ * The member this permissions refer to
+ * @type {GuildMember}
+ */
+ this.member = member;
+
+ /**
+ * A number representing the packed permissions
+ * @type {number}
+ */
+ this.raw = raw;
+ }
+
+ /**
+ * Get an object mapping permission name, e.g. `READ_MESSAGES` to a boolean - whether the user
+ * can perform this or not.
+ * @returns {Object<string, boolean>}
+ */
+ serialize() {
+ const serializedPermissions = {};
+ for (const permissionName in Constants.PermissionFlags) {
+ serializedPermissions[permissionName] = this.hasPermission(permissionName);
+ }
+ return serializedPermissions;
+ }
+
+ /**
+ * Checks whether the user has a certain permission, e.g. `READ_MESSAGES`.
+ * @param {PermissionResolvable} permission The permission to check for
+ * @param {boolean} [explicit=false] Whether to require the user to explicitly have the exact permission
+ * @returns {boolean}
+ */
+ hasPermission(permission, explicit = false) {
+ permission = this.member.client.resolver.resolvePermission(permission);
+ if (!explicit && (this.raw & Constants.PermissionFlags.ADMINISTRATOR) > 0) return true;
+ return (this.raw & permission) > 0;
+ }
+
+ /**
+ * Checks whether the user has all specified permissions.
+ * @param {PermissionResolvable[]} permissions The permissions to check for
+ * @param {boolean} [explicit=false] Whether to require the user to explicitly have the exact permissions
+ * @returns {boolean}
+ */
+ hasPermissions(permissions, explicit = false) {
+ return permissions.every(p => this.hasPermission(p, explicit));
+ }
+
+ /**
+ * Checks whether the user has all specified permissions, and lists any missing permissions.
+ * @param {PermissionResolvable[]} permissions The permissions to check for
+ * @param {boolean} [explicit=false] Whether to require the user to explicitly have the exact permissions
+ * @returns {PermissionResolvable[]}
+ */
+ missingPermissions(permissions, explicit = false) {
+ return permissions.filter(p => !this.hasPermission(p, explicit));
+ }
+}
+
+module.exports = EvaluatedPermissions;
diff --git a/node_modules/discord.js/src/structures/GroupDMChannel.js b/node_modules/discord.js/src/structures/GroupDMChannel.js
new file mode 100644
index 0000000..84fe4f9
--- /dev/null
+++ b/node_modules/discord.js/src/structures/GroupDMChannel.js
@@ -0,0 +1,144 @@
+const Channel = require('./Channel');
+const TextBasedChannel = require('./interface/TextBasedChannel');
+const Collection = require('../util/Collection');
+
+/*
+{ type: 3,
+ recipients:
+ [ { username: 'Charlie',
+ id: '123',
+ discriminator: '6631',
+ avatar: '123' },
+ { username: 'Ben',
+ id: '123',
+ discriminator: '2055',
+ avatar: '123' },
+ { username: 'Adam',
+ id: '123',
+ discriminator: '2406',
+ avatar: '123' } ],
+ owner_id: '123',
+ name: null,
+ last_message_id: '123',
+ id: '123',
+ icon: null }
+*/
+
+/**
+ * Represents a Group DM on Discord
+ * @extends {Channel}
+ * @implements {TextBasedChannel}
+ */
+class GroupDMChannel extends Channel {
+ constructor(client, data) {
+ super(client, data);
+ this.type = 'group';
+ this.messages = new Collection();
+ this._typing = new Map();
+ }
+
+ setup(data) {
+ super.setup(data);
+
+ /**
+ * The name of this Group DM, can be null if one isn't set.
+ * @type {string}
+ */
+ this.name = data.name;
+
+ /**
+ * A hash of the Group DM icon.
+ * @type {string}
+ */
+ this.icon = data.icon;
+
+ /**
+ * The user ID of this Group DM's owner.
+ * @type {string}
+ */
+ this.ownerID = data.owner_id;
+
+ if (!this.recipients) {
+ /**
+ * A collection of the recipients of this DM, mapped by their ID.
+ * @type {Collection<string, User>}
+ */
+ this.recipients = new Collection();
+ }
+
+ if (data.recipients) {
+ for (const recipient of data.recipients) {
+ const user = this.client.dataManager.newUser(recipient);
+ this.recipients.set(user.id, user);
+ }
+ }
+
+ this.lastMessageID = data.last_message_id;
+ }
+
+ /**
+ * The owner of this Group DM.
+ * @type {User}
+ * @readonly
+ */
+ get owner() {
+ return this.client.users.get(this.ownerID);
+ }
+
+ /**
+ * Whether this channel equals another channel. It compares all properties, so for most operations
+ * it is advisable to just compare `channel.id === channel2.id` as it is much faster and is often
+ * what most users need.
+ * @param {GroupDMChannel} channel Channel to compare with
+ * @returns {boolean}
+ */
+ equals(channel) {
+ const equal = channel &&
+ this.id === channel.id &&
+ this.name === channel.name &&
+ this.icon === channel.icon &&
+ this.ownerID === channel.ownerID;
+
+ if (equal) {
+ return this.recipients.equals(channel.recipients);
+ }
+
+ return equal;
+ }
+
+ /**
+ * When concatenated with a string, this automatically concatenates the channel's name instead of the Channel object.
+ * @returns {string}
+ * @example
+ * // logs: Hello from My Group DM!
+ * console.log(`Hello from ${channel}!`);
+ * @example
+ * // logs: Hello from My Group DM!
+ * console.log(`Hello from ' + channel + '!');
+ */
+ toString() {
+ return this.name;
+ }
+
+ // These are here only for documentation purposes - they are implemented by TextBasedChannel
+ send() { return; }
+ sendMessage() { return; }
+ sendEmbed() { return; }
+ sendFile() { return; }
+ sendCode() { return; }
+ fetchMessage() { return; }
+ fetchMessages() { return; }
+ fetchPinnedMessages() { return; }
+ startTyping() { return; }
+ stopTyping() { return; }
+ get typing() { return; }
+ get typingCount() { return; }
+ createCollector() { return; }
+ awaitMessages() { return; }
+ bulkDelete() { return; }
+ _cacheMessage() { return; }
+}
+
+TextBasedChannel.applyToClass(GroupDMChannel, true);
+
+module.exports = GroupDMChannel;
diff --git a/node_modules/discord.js/src/structures/Guild.js b/node_modules/discord.js/src/structures/Guild.js
new file mode 100644
index 0000000..acd5b6f
--- /dev/null
+++ b/node_modules/discord.js/src/structures/Guild.js
@@ -0,0 +1,851 @@
+const User = require('./User');
+const Role = require('./Role');
+const Emoji = require('./Emoji');
+const Presence = require('./Presence').Presence;
+const GuildMember = require('./GuildMember');
+const Constants = require('../util/Constants');
+const Collection = require('../util/Collection');
+const cloneObject = require('../util/CloneObject');
+const arraysEqual = require('../util/ArraysEqual');
+
+/**
+ * Represents a guild (or a server) on Discord.
+ * <info>It's recommended to see if a guild is available before performing operations or reading data from it. You can
+ * check this with `guild.available`.</info>
+ */
+class Guild {
+ constructor(client, data) {
+ /**
+ * The Client that created the instance of the the Guild.
+ * @name Guild#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: client });
+
+ /**
+ * A collection of members that are in this guild. The key is the member's ID, the value is the member.
+ * @type {Collection<string, GuildMember>}
+ */
+ this.members = new Collection();
+
+ /**
+ * A collection of channels that are in this guild. The key is the channel's ID, the value is the channel.
+ * @type {Collection<string, GuildChannel>}
+ */
+ this.channels = new Collection();
+
+ /**
+ * A collection of roles that are in this guild. The key is the role's ID, the value is the role.
+ * @type {Collection<string, Role>}
+ */
+ this.roles = new Collection();
+
+ /**
+ * A collection of presences in this guild
+ * @type {Collection<string, Presence>}
+ */
+ this.presences = new Collection();
+
+ if (!data) return;
+ if (data.unavailable) {
+ /**
+ * Whether the guild is available to access. If it is not available, it indicates a server outage.
+ * @type {boolean}
+ */
+ this.available = false;
+
+ /**
+ * The Unique ID of the Guild, useful for comparisons.
+ * @type {string}
+ */
+ this.id = data.id;
+ } else {
+ this.available = true;
+ this.setup(data);
+ }
+ }
+
+ /**
+ * Sets up the Guild
+ * @param {*} data The raw data of the guild
+ * @private
+ */
+ setup(data) {
+ /**
+ * The name of the guild
+ * @type {string}
+ */
+ this.name = data.name;
+
+ /**
+ * The hash of the guild icon, or null if there is no icon.
+ * @type {?string}
+ */
+ this.icon = data.icon;
+
+ /**
+ * The hash of the guild splash image, or null if no splash (VIP only)
+ * @type {?string}
+ */
+ this.splash = data.splash;
+
+ /**
+ * The region the guild is located in
+ * @type {string}
+ */
+ this.region = data.region;
+
+ /**
+ * The full amount of members in this guild as of `READY`
+ * @type {number}
+ */
+ this.memberCount = data.member_count || this.memberCount;
+
+ /**
+ * Whether the guild is "large" (has more than 250 members)
+ * @type {boolean}
+ */
+ this.large = data.large || this.large;
+
+ /**
+ * An array of guild features.
+ * @type {Object[]}
+ */
+ this.features = data.features;
+
+ /**
+ * The ID of the application that created this guild (if applicable)
+ * @type {?string}
+ */
+ this.applicationID = data.application_id;
+
+ /**
+ * A collection of emojis that are in this guild. The key is the emoji's ID, the value is the emoji.
+ * @type {Collection<string, Emoji>}
+ */
+ this.emojis = new Collection();
+ for (const emoji of data.emojis) this.emojis.set(emoji.id, new Emoji(this, emoji));
+
+ /**
+ * The time in seconds before a user is counted as "away from keyboard".
+ * @type {?number}
+ */
+ this.afkTimeout = data.afk_timeout;
+
+ /**
+ * The ID of the voice channel where AFK members are moved.
+ * @type {?string}
+ */
+ this.afkChannelID = data.afk_channel_id;
+
+ /**
+ * Whether embedded images are enabled on this guild.
+ * @type {boolean}
+ */
+ this.embedEnabled = data.embed_enabled;
+
+ /**
+ * The verification level of the guild.
+ * @type {number}
+ */
+ this.verificationLevel = data.verification_level;
+
+ /**
+ * The timestamp the client user joined the guild at
+ * @type {number}
+ */
+ this.joinedTimestamp = data.joined_at ? new Date(data.joined_at).getTime() : this.joinedTimestamp;
+
+ this.id = data.id;
+ this.available = !data.unavailable;
+ this.features = data.features || this.features || [];
+
+ if (data.members) {
+ this.members.clear();
+ for (const guildUser of data.members) this._addMember(guildUser, false);
+ }
+
+ if (data.owner_id) {
+ /**
+ * The user ID of this guild's owner.
+ * @type {string}
+ */
+ this.ownerID = data.owner_id;
+ }
+
+ if (data.channels) {
+ this.channels.clear();
+ for (const channel of data.channels) this.client.dataManager.newChannel(channel, this);
+ }
+
+ if (data.roles) {
+ this.roles.clear();
+ for (const role of data.roles) {
+ const newRole = new Role(this, role);
+ this.roles.set(newRole.id, newRole);
+ }
+ }
+
+ if (data.presences) {
+ for (const presence of data.presences) {
+ this._setPresence(presence.user.id, presence);
+ }
+ }
+
+ this._rawVoiceStates = new Collection();
+ if (data.voice_states) {
+ for (const voiceState of data.voice_states) {
+ this._rawVoiceStates.set(voiceState.user_id, voiceState);
+ const member = this.members.get(voiceState.user_id);
+ if (member) {
+ member.serverMute = voiceState.mute;
+ member.serverDeaf = voiceState.deaf;
+ member.selfMute = voiceState.self_mute;
+ member.selfDeaf = voiceState.self_deaf;
+ member.voiceSessionID = voiceState.session_id;
+ member.voiceChannelID = voiceState.channel_id;
+ this.channels.get(voiceState.channel_id).members.set(member.user.id, member);
+ }
+ }
+ }
+ }
+
+ /**
+ * The timestamp the guild was created at
+ * @type {number}
+ * @readonly
+ */
+ get createdTimestamp() {
+ return (this.id / 4194304) + 1420070400000;
+ }
+
+ /**
+ * The time the guild was created
+ * @type {Date}
+ * @readonly
+ */
+ get createdAt() {
+ return new Date(this.createdTimestamp);
+ }
+
+ /**
+ * The time the client user joined the guild
+ * @type {Date}
+ * @readonly
+ */
+ get joinedAt() {
+ return new Date(this.joinedTimestamp);
+ }
+
+ /**
+ * Gets the URL to this guild's icon (if it has one, otherwise it returns null)
+ * @type {?string}
+ * @readonly
+ */
+ get iconURL() {
+ if (!this.icon) return null;
+ return Constants.Endpoints.guildIcon(this.id, this.icon);
+ }
+
+ /**
+ * Gets the URL to this guild's splash (if it has one, otherwise it returns null)
+ * @type {?string}
+ * @readonly
+ */
+ get splashURL() {
+ if (!this.splash) return null;
+ return Constants.Endpoints.guildSplash(this.id, this.splash);
+ }
+
+ /**
+ * The owner of the guild
+ * @type {GuildMember}
+ * @readonly
+ */
+ get owner() {
+ return this.members.get(this.ownerID);
+ }
+
+ /**
+ * If the client is connected to any voice channel in this guild, this will be the relevant VoiceConnection.
+ * @type {?VoiceConnection}
+ * @readonly
+ */
+ get voiceConnection() {
+ if (this.client.browser) return null;
+ return this.client.voice.connections.get(this.id) || null;
+ }
+
+ /**
+ * The `#general` GuildChannel of the server.
+ * @type {GuildChannel}
+ * @readonly
+ */
+ get defaultChannel() {
+ return this.channels.get(this.id);
+ }
+
+ /**
+ * Returns the GuildMember form of a User object, if the user is present in the guild.
+ * @param {UserResolvable} user The user that you want to obtain the GuildMember of
+ * @returns {?GuildMember}
+ * @example
+ * // get the guild member of a user
+ * const member = guild.member(message.author);
+ */
+ member(user) {
+ return this.client.resolver.resolveGuildMember(this, user);
+ }
+
+ /**
+ * Fetch a collection of banned users in this guild.
+ * @returns {Promise<Collection<string, User>>}
+ */
+ fetchBans() {
+ return this.client.rest.methods.getGuildBans(this);
+ }
+
+ /**
+ * Fetch a collection of invites to this guild. Resolves with a collection mapping invites by their codes.
+ * @returns {Promise<Collection<string, Invite>>}
+ */
+ fetchInvites() {
+ return this.client.rest.methods.getGuildInvites(this);
+ }
+
+ /**
+ * Fetch all webhooks for the guild.
+ * @returns {Collection<Webhook>}
+ */
+ fetchWebhooks() {
+ return this.client.rest.methods.getGuildWebhooks(this);
+ }
+
+ /**
+ * Fetch a single guild member from a user.
+ * @param {UserResolvable} user The user to fetch the member for
+ * @returns {Promise<GuildMember>}
+ */
+ fetchMember(user) {
+ if (this._fetchWaiter) return Promise.reject(new Error('Already fetching guild members.'));
+ user = this.client.resolver.resolveUser(user);
+ if (!user) return Promise.reject(new Error('User is not cached. Use Client.fetchUser first.'));
+ if (this.members.has(user.id)) return Promise.resolve(this.members.get(user.id));
+ return this.client.rest.methods.getGuildMember(this, user);
+ }
+
+ /**
+ * Fetches all the members in the guild, even if they are offline. If the guild has less than 250 members,
+ * this should not be necessary.
+ * @param {string} [query=''] An optional query to provide when fetching members
+ * @returns {Promise<Guild>}
+ */
+ fetchMembers(query = '') {
+ return new Promise((resolve, reject) => {
+ if (this._fetchWaiter) throw new Error('Already fetching guild members in ${this.id}.');
+ if (this.memberCount === this.members.size) {
+ resolve(this);
+ return;
+ }
+ this._fetchWaiter = resolve;
+ this.client.ws.send({
+ op: Constants.OPCodes.REQUEST_GUILD_MEMBERS,
+ d: {
+ guild_id: this.id,
+ query,
+ limit: 0,
+ },
+ });
+ this._checkChunks();
+ this.client.setTimeout(() => reject(new Error('Members didn\'t arrive in time.')), 120 * 1000);
+ });
+ }
+
+ /**
+ * The data for editing a guild
+ * @typedef {Object} GuildEditData
+ * @property {string} [name] The name of the guild
+ * @property {string} [region] The region of the guild
+ * @property {number} [verificationLevel] The verification level of the guild
+ * @property {ChannelResolvable} [afkChannel] The AFK channel of the guild
+ * @property {number} [afkTimeout] The AFK timeout of the guild
+ * @property {Base64Resolvable} [icon] The icon of the guild
+ * @property {GuildMemberResolvable} [owner] The owner of the guild
+ * @property {Base64Resolvable} [splash] The splash screen of the guild
+ */
+
+ /**
+ * Updates the Guild with new information - e.g. a new name.
+ * @param {GuildEditData} data The data to update the guild with
+ * @returns {Promise<Guild>}
+ * @example
+ * // set the guild name and region
+ * guild.edit({
+ * name: 'Discord Guild',
+ * region: 'london',
+ * })
+ * .then(updated => console.log(`New guild name ${updated.name} in region ${updated.region}`))
+ * .catch(console.error);
+ */
+ edit(data) {
+ return this.client.rest.methods.updateGuild(this, data);
+ }
+
+ /**
+ * Edit the name of the guild.
+ * @param {string} name The new name of the guild
+ * @returns {Promise<Guild>}
+ * @example
+ * // edit the guild name
+ * guild.setName('Discord Guild')
+ * .then(updated => console.log(`Updated guild name to ${guild.name}`))
+ * .catch(console.error);
+ */
+ setName(name) {
+ return this.edit({ name });
+ }
+
+ /**
+ * Edit the region of the guild.
+ * @param {string} region The new region of the guild.
+ * @returns {Promise<Guild>}
+ * @example
+ * // edit the guild region
+ * guild.setRegion('london')
+ * .then(updated => console.log(`Updated guild region to ${guild.region}`))
+ * .catch(console.error);
+ */
+ setRegion(region) {
+ return this.edit({ region });
+ }
+
+ /**
+ * Edit the verification level of the guild.
+ * @param {number} verificationLevel The new verification level of the guild
+ * @returns {Promise<Guild>}
+ * @example
+ * // edit the guild verification level
+ * guild.setVerificationLevel(1)
+ * .then(updated => console.log(`Updated guild verification level to ${guild.verificationLevel}`))
+ * .catch(console.error);
+ */
+ setVerificationLevel(verificationLevel) {
+ return this.edit({ verificationLevel });
+ }
+
+ /**
+ * Edit the AFK channel of the guild.
+ * @param {ChannelResolvable} afkChannel The new AFK channel
+ * @returns {Promise<Guild>}
+ * @example
+ * // edit the guild AFK channel
+ * guild.setAFKChannel(channel)
+ * .then(updated => console.log(`Updated guild AFK channel to ${guild.afkChannel}`))
+ * .catch(console.error);
+ */
+ setAFKChannel(afkChannel) {
+ return this.edit({ afkChannel });
+ }
+
+ /**
+ * Edit the AFK timeout of the guild.
+ * @param {number} afkTimeout The time in seconds that a user must be idle to be considered AFK
+ * @returns {Promise<Guild>}
+ * @example
+ * // edit the guild AFK channel
+ * guild.setAFKTimeout(60)
+ * .then(updated => console.log(`Updated guild AFK timeout to ${guild.afkTimeout}`))
+ * .catch(console.error);
+ */
+ setAFKTimeout(afkTimeout) {
+ return this.edit({ afkTimeout });
+ }
+
+ /**
+ * Set a new guild icon.
+ * @param {Base64Resolvable} icon The new icon of the guild
+ * @returns {Promise<Guild>}
+ * @example
+ * // edit the guild icon
+ * guild.setIcon(fs.readFileSync('./icon.png'))
+ * .then(updated => console.log('Updated the guild icon'))
+ * .catch(console.error);
+ */
+ setIcon(icon) {
+ return this.edit({ icon });
+ }
+
+ /**
+ * Sets a new owner of the guild.
+ * @param {GuildMemberResolvable} owner The new owner of the guild
+ * @returns {Promise<Guild>}
+ * @example
+ * // edit the guild owner
+ * guild.setOwner(guilds.members[0])
+ * .then(updated => console.log(`Updated the guild owner to ${updated.owner.username}`))
+ * .catch(console.error);
+ */
+ setOwner(owner) {
+ return this.edit({ owner });
+ }
+
+ /**
+ * Set a new guild splash screen.
+ * @param {Base64Resolvable} splash The new splash screen of the guild
+ * @returns {Promise<Guild>}
+ * @example
+ * // edit the guild splash
+ * guild.setIcon(fs.readFileSync('./splash.png'))
+ * .then(updated => console.log('Updated the guild splash'))
+ * .catch(console.error);
+ */
+ setSplash(splash) {
+ return this.edit({ splash });
+ }
+
+ /**
+ * Bans a user from the guild.
+ * @param {UserResolvable} user The user to ban
+ * @param {number} [deleteDays=0] The amount of days worth of messages from this user that should
+ * also be deleted. Between `0` and `7`.
+ * @returns {Promise<GuildMember|User|string>} Result object will be resolved as specifically as possible.
+ * If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot
+ * be resolved, the user ID will be the result.
+ * @example
+ * // ban a user
+ * guild.ban('123123123123');
+ */
+ ban(user, deleteDays = 0) {
+ return this.client.rest.methods.banGuildMember(this, user, deleteDays);
+ }
+
+ /**
+ * Unbans a user from the guild.
+ * @param {UserResolvable} user The user to unban
+ * @returns {Promise<User>}
+ * @example
+ * // unban a user
+ * guild.unban('123123123123')
+ * .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`))
+ * .catch(reject);
+ */
+ unban(user) {
+ return this.client.rest.methods.unbanGuildMember(this, user);
+ }
+
+ /**
+ * Prunes members from the guild based on how long they have been inactive.
+ * @param {number} days Number of days of inactivity required to kick
+ * @param {boolean} [dry=false] If true, will return number of users that will be kicked, without actually doing it
+ * @returns {Promise<number>} The number of members that were/will be kicked
+ * @example
+ * // see how many members will be pruned
+ * guild.pruneMembers(12, true)
+ * .then(pruned => console.log(`This will prune ${pruned} people!`);
+ * .catch(console.error);
+ * @example
+ * // actually prune the members
+ * guild.pruneMembers(12)
+ * .then(pruned => console.log(`I just pruned ${pruned} people!`);
+ * .catch(console.error);
+ */
+ pruneMembers(days, dry = false) {
+ if (typeof days !== 'number') throw new TypeError('Days must be a number.');
+ return this.client.rest.methods.pruneGuildMembers(this, days, dry);
+ }
+
+ /**
+ * Syncs this guild (already done automatically every 30 seconds).
+ * <warn>This is only available when using a user account.</warn>
+ */
+ sync() {
+ if (!this.client.user.bot) this.client.syncGuilds([this]);
+ }
+
+ /**
+ * Creates a new channel in the guild.
+ * @param {string} name The name of the new channel
+ * @param {string} type The type of the new channel, either `text` or `voice`
+ * @param {Array<PermissionOverwrites|Object>} overwrites Permission overwrites to apply to the new channel
+ * @returns {Promise<TextChannel|VoiceChannel>}
+ * @example
+ * // create a new text channel
+ * guild.createChannel('new-general', 'text')
+ * .then(channel => console.log(`Created new channel ${channel}`))
+ * .catch(console.error);
+ */
+ createChannel(name, type, overwrites) {
+ return this.client.rest.methods.createChannel(this, name, type, overwrites);
+ }
+
+ /**
+ * Creates a new role in the guild, and optionally updates it with the given information.
+ * @param {RoleData} [data] The data to update the role with
+ * @returns {Promise<Role>}
+ * @example
+ * // create a new role
+ * guild.createRole()
+ * .then(role => console.log(`Created role ${role}`))
+ * .catch(console.error);
+ * @example
+ * // create a new role with data
+ * guild.createRole({ name: 'Super Cool People' })
+ * .then(role => console.log(`Created role ${role}`))
+ * .catch(console.error)
+ */
+ createRole(data) {
+ const create = this.client.rest.methods.createGuildRole(this);
+ if (!data) return create;
+ return create.then(role => role.edit(data));
+ }
+
+ /**
+ * Creates a new custom emoji in the guild.
+ * @param {BufferResolvable} attachment The image for the emoji.
+ * @param {string} name The name for the emoji.
+ * @returns {Promise<Emoji>} The created emoji.
+ * @example
+ * // create a new emoji from a url
+ * guild.createEmoji('https://i.imgur.com/w3duR07.png', 'rip')
+ * .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
+ * .catch(console.error);
+ * @example
+ * // create a new emoji from a file on your computer
+ * guild.createEmoji('./memes/banana.png', 'banana')
+ * .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
+ * .catch(console.error);
+ */
+ createEmoji(attachment, name) {
+ return new Promise(resolve => {
+ if (attachment.startsWith('data:')) {
+ resolve(this.client.rest.methods.createEmoji(this, attachment, name));
+ } else {
+ this.client.resolver.resolveBuffer(attachment).then(data =>
+ resolve(this.client.rest.methods.createEmoji(this, data, name))
+ );
+ }
+ });
+ }
+
+ /**
+ * Delete an emoji.
+ * @param {Emoji|string} emoji The emoji to delete.
+ * @returns {Promise}
+ */
+ deleteEmoji(emoji) {
+ if (!(emoji instanceof Emoji)) emoji = this.emojis.get(emoji);
+ return this.client.rest.methods.deleteEmoji(emoji);
+ }
+
+ /**
+ * Causes the Client to leave the guild.
+ * @returns {Promise<Guild>}
+ * @example
+ * // leave a guild
+ * guild.leave()
+ * .then(g => console.log(`Left the guild ${g}`))
+ * .catch(console.error);
+ */
+ leave() {
+ return this.client.rest.methods.leaveGuild(this);
+ }
+
+ /**
+ * Causes the Client to delete the guild.
+ * @returns {Promise<Guild>}
+ * @example
+ * // delete a guild
+ * guild.delete()
+ * .then(g => console.log(`Deleted the guild ${g}`))
+ * .catch(console.error);
+ */
+ delete() {
+ return this.client.rest.methods.deleteGuild(this);
+ }
+
+ /**
+ * Set the position of a role in this guild
+ * @param {string|Role} role the role to edit, can be a role object or a role ID.
+ * @param {number} position the new position of the role
+ * @returns {Promise<Guild>}
+ */
+ setRolePosition(role, position) {
+ if (typeof role === 'string') {
+ role = this.roles.get(role);
+ if (!role) return Promise.reject(new Error('Supplied role is not a role or string.'));
+ }
+
+ position = Number(position);
+ if (isNaN(position)) return Promise.reject(new Error('Supplied position is not a number.'));
+
+ const lowestAffected = Math.min(role.position, position);
+ const highestAffected = Math.max(role.position, position);
+
+ const rolesToUpdate = this.roles.filter(r => r.position >= lowestAffected && r.position <= highestAffected);
+
+ // stop role positions getting stupidly inflated
+ if (position > role.position) {
+ position = rolesToUpdate.first().position;
+ } else {
+ position = rolesToUpdate.last().position;
+ }
+
+ const updatedRoles = [];
+
+ for (const uRole of rolesToUpdate.values()) {
+ updatedRoles.push({
+ id: uRole.id,
+ position: uRole.id === role.id ? position : uRole.position + (position < role.position ? 1 : -1),
+ });
+ }
+
+ return this.client.rest.methods.setRolePositions(this.id, updatedRoles);
+ }
+
+ /**
+ * Whether this Guild equals another Guild. It compares all properties, so for most operations
+ * it is advisable to just compare `guild.id === guild2.id` as it is much faster and is often
+ * what most users need.
+ * @param {Guild} guild Guild to compare with
+ * @returns {boolean}
+ */
+ equals(guild) {
+ let equal =
+ guild &&
+ this.id === guild.id &&
+ this.available === !guild.unavailable &&
+ this.splash === guild.splash &&
+ this.region === guild.region &&
+ this.name === guild.name &&
+ this.memberCount === guild.member_count &&
+ this.large === guild.large &&
+ this.icon === guild.icon &&
+ arraysEqual(this.features, guild.features) &&
+ this.ownerID === guild.owner_id &&
+ this.verificationLevel === guild.verification_level &&
+ this.embedEnabled === guild.embed_enabled;
+
+ if (equal) {
+ if (this.embedChannel) {
+ if (this.embedChannel.id !== guild.embed_channel_id) equal = false;
+ } else if (guild.embed_channel_id) {
+ equal = false;
+ }
+ }
+
+ return equal;
+ }
+
+ /**
+ * When concatenated with a string, this automatically concatenates the guild's name instead of the Guild object.
+ * @returns {string}
+ * @example
+ * // logs: Hello from My Guild!
+ * console.log(`Hello from ${guild}!`);
+ * @example
+ * // logs: Hello from My Guild!
+ * console.log(`Hello from ' + guild + '!');
+ */
+ toString() {
+ return this.name;
+ }
+
+ _addMember(guildUser, emitEvent = true) {
+ const existing = this.members.has(guildUser.user.id);
+ if (!(guildUser.user instanceof User)) guildUser.user = this.client.dataManager.newUser(guildUser.user);
+
+ guildUser.joined_at = guildUser.joined_at || 0;
+ const member = new GuildMember(this, guildUser);
+ this.members.set(member.id, member);
+
+ if (this._rawVoiceStates && this._rawVoiceStates.has(member.user.id)) {
+ const voiceState = this._rawVoiceStates.get(member.user.id);
+ member.serverMute = voiceState.mute;
+ member.serverDeaf = voiceState.deaf;
+ member.selfMute = voiceState.self_mute;
+ member.selfDeaf = voiceState.self_deaf;
+ member.voiceSessionID = voiceState.session_id;
+ member.voiceChannelID = voiceState.channel_id;
+ if (this.client.channels.has(voiceState.channel_id)) {
+ this.client.channels.get(voiceState.channel_id).members.set(member.user.id, member);
+ } else {
+ this.client.emit('warn', `Member ${member.id} added in guild ${this.id} with an uncached voice channel`);
+ }
+ }
+
+ /**
+ * Emitted whenever a user joins a guild.
+ * @event Client#guildMemberAdd
+ * @param {GuildMember} member The member that has joined a guild
+ */
+ if (this.client.ws.status === Constants.Status.READY && emitEvent && !existing) {
+ this.client.emit(Constants.Events.GUILD_MEMBER_ADD, member);
+ }
+
+ this._checkChunks();
+ return member;
+ }
+
+ _updateMember(member, data) {
+ const oldMember = cloneObject(member);
+
+ if (data.roles) member._roles = data.roles;
+ if (typeof data.nick !== 'undefined') member.nickname = data.nick;
+
+ const notSame = member.nickname !== oldMember.nickname || !arraysEqual(member._roles, oldMember._roles);
+
+ if (this.client.ws.status === Constants.Status.READY && notSame) {
+ /**
+ * Emitted whenever a guild member changes - i.e. new role, removed role, nickname
+ * @event Client#guildMemberUpdate
+ * @param {GuildMember} oldMember The member before the update
+ * @param {GuildMember} newMember The member after the update
+ */
+ this.client.emit(Constants.Events.GUILD_MEMBER_UPDATE, oldMember, member);
+ }
+
+ return {
+ old: oldMember,
+ mem: member,
+ };
+ }
+
+ _removeMember(guildMember) {
+ this.members.delete(guildMember.id);
+ this._checkChunks();
+ }
+
+ _memberSpeakUpdate(user, speaking) {
+ const member = this.members.get(user);
+ if (member && member.speaking !== speaking) {
+ member.speaking = speaking;
+ /**
+ * Emitted once a guild member starts/stops speaking
+ * @event Client#guildMemberSpeaking
+ * @param {GuildMember} member The member that started/stopped speaking
+ * @param {boolean} speaking Whether or not the member is speaking
+ */
+ this.client.emit(Constants.Events.GUILD_MEMBER_SPEAKING, member, speaking);
+ }
+ }
+
+ _setPresence(id, presence) {
+ if (this.presences.get(id)) {
+ this.presences.get(id).update(presence);
+ return;
+ }
+ this.presences.set(id, new Presence(presence));
+ }
+
+ _checkChunks() {
+ if (this._fetchWaiter) {
+ if (this.members.size === this.memberCount) {
+ this._fetchWaiter(this);
+ this._fetchWaiter = null;
+ }
+ }
+ }
+}
+
+module.exports = Guild;
diff --git a/node_modules/discord.js/src/structures/GuildChannel.js b/node_modules/discord.js/src/structures/GuildChannel.js
new file mode 100644
index 0000000..a930789
--- /dev/null
+++ b/node_modules/discord.js/src/structures/GuildChannel.js
@@ -0,0 +1,299 @@
+const Channel = require('./Channel');
+const Role = require('./Role');
+const PermissionOverwrites = require('./PermissionOverwrites');
+const EvaluatedPermissions = require('./EvaluatedPermissions');
+const Constants = require('../util/Constants');
+const Collection = require('../util/Collection');
+
+/**
+ * Represents a guild channel (i.e. text channels and voice channels)
+ * @extends {Channel}
+ */
+class GuildChannel extends Channel {
+ constructor(guild, data) {
+ super(guild.client, data);
+
+ /**
+ * The guild the channel is in
+ * @type {Guild}
+ */
+ this.guild = guild;
+ }
+
+ setup(data) {
+ super.setup(data);
+
+ /**
+ * The name of the guild channel
+ * @type {string}
+ */
+ this.name = data.name;
+
+ /**
+ * The position of the channel in the list.
+ * @type {number}
+ */
+ this.position = data.position;
+
+ /**
+ * A map of permission overwrites in this channel for roles and users.
+ * @type {Collection<string, PermissionOverwrites>}
+ */
+ this.permissionOverwrites = new Collection();
+ if (data.permission_overwrites) {
+ for (const overwrite of data.permission_overwrites) {
+ this.permissionOverwrites.set(overwrite.id, new PermissionOverwrites(this, overwrite));
+ }
+ }
+ }
+
+ /**
+ * Gets the overall set of permissions for a user in this channel, taking into account roles and permission
+ * overwrites.
+ * @param {GuildMemberResolvable} member The user that you want to obtain the overall permissions for
+ * @returns {?EvaluatedPermissions}
+ */
+ permissionsFor(member) {
+ member = this.client.resolver.resolveGuildMember(this.guild, member);
+ if (!member) return null;
+ if (member.id === this.guild.ownerID) return new EvaluatedPermissions(member, Constants.ALL_PERMISSIONS);
+
+ let permissions = 0;
+
+ const roles = member.roles;
+ for (const role of roles.values()) permissions |= role.permissions;
+
+ const overwrites = this.overwritesFor(member, true, roles);
+ for (const overwrite of overwrites.role.concat(overwrites.member)) {
+ permissions &= ~overwrite.deny;
+ permissions |= overwrite.allow;
+ }
+
+ const admin = Boolean(permissions & Constants.PermissionFlags.ADMINISTRATOR);
+ if (admin) permissions = Constants.ALL_PERMISSIONS;
+
+ return new EvaluatedPermissions(member, permissions);
+ }
+
+ overwritesFor(member, verified = false, roles = null) {
+ if (!verified) member = this.client.resolver.resolveGuildMember(this.guild, member);
+ if (!member) return [];
+
+ roles = roles || member.roles;
+ const roleOverwrites = [];
+ const memberOverwrites = [];
+
+ for (const overwrite of this.permissionOverwrites.values()) {
+ if (overwrite.id === member.id) {
+ memberOverwrites.push(overwrite);
+ } else if (roles.has(overwrite.id)) {
+ roleOverwrites.push(overwrite);
+ }
+ }
+
+ return {
+ role: roleOverwrites,
+ member: memberOverwrites,
+ };
+ }
+
+ /**
+ * An object mapping permission flags to `true` (enabled) or `false` (disabled)
+ * ```js
+ * {
+ * 'SEND_MESSAGES': true,
+ * 'ATTACH_FILES': false,
+ * }
+ * ```
+ * @typedef {Object} PermissionOverwriteOptions
+ */
+
+ /**
+ * Overwrites the permissions for a user or role in this channel.
+ * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update
+ * @param {PermissionOverwriteOptions} options The configuration for the update
+ * @returns {Promise}
+ * @example
+ * // overwrite permissions for a message author
+ * message.channel.overwritePermissions(message.author, {
+ * SEND_MESSAGES: false
+ * })
+ * .then(() => console.log('Done!'))
+ * .catch(console.error);
+ */
+ overwritePermissions(userOrRole, options) {
+ const payload = {
+ allow: 0,
+ deny: 0,
+ };
+
+ if (userOrRole instanceof Role) {
+ payload.type = 'role';
+ } else if (this.guild.roles.has(userOrRole)) {
+ userOrRole = this.guild.roles.get(userOrRole);
+ payload.type = 'role';
+ } else {
+ userOrRole = this.client.resolver.resolveUser(userOrRole);
+ payload.type = 'member';
+ if (!userOrRole) return Promise.reject(new TypeError('Supplied parameter was neither a User nor a Role.'));
+ }
+
+ payload.id = userOrRole.id;
+
+ const prevOverwrite = this.permissionOverwrites.get(userOrRole.id);
+
+ if (prevOverwrite) {
+ payload.allow = prevOverwrite.allow;
+ payload.deny = prevOverwrite.deny;
+ }
+
+ for (const perm in options) {
+ if (options[perm] === true) {
+ payload.allow |= Constants.PermissionFlags[perm] || 0;
+ payload.deny &= ~(Constants.PermissionFlags[perm] || 0);
+ } else if (options[perm] === false) {
+ payload.allow &= ~(Constants.PermissionFlags[perm] || 0);
+ payload.deny |= Constants.PermissionFlags[perm] || 0;
+ } else if (options[perm] === null) {
+ payload.allow &= ~(Constants.PermissionFlags[perm] || 0);
+ payload.deny &= ~(Constants.PermissionFlags[perm] || 0);
+ }
+ }
+
+ return this.client.rest.methods.setChannelOverwrite(this, payload);
+ }
+
+ /**
+ * The data for a guild channel
+ * @typedef {Object} ChannelData
+ * @property {string} [name] The name of the channel
+ * @property {number} [position] The position of the channel
+ * @property {string} [topic] The topic of the text channel
+ * @property {number} [bitrate] The bitrate of the voice channel
+ * @property {number} [userLimit] The user limit of the channel
+ */
+
+ /**
+ * Edits the channel
+ * @param {ChannelData} data The new data for the channel
+ * @returns {Promise<GuildChannel>}
+ * @example
+ * // edit a channel
+ * channel.edit({name: 'new-channel'})
+ * .then(c => console.log(`Edited channel ${c}`))
+ * .catch(console.error);
+ */
+ edit(data) {
+ return this.client.rest.methods.updateChannel(this, data);
+ }
+
+ /**
+ * Set a new name for the guild channel
+ * @param {string} name The new name for the guild channel
+ * @returns {Promise<GuildChannel>}
+ * @example
+ * // set a new channel name
+ * channel.setName('not_general')
+ * .then(newChannel => console.log(`Channel's new name is ${newChannel.name}`))
+ * .catch(console.error);
+ */
+ setName(name) {
+ return this.edit({ name });
+ }
+
+ /**
+ * Set a new position for the guild channel
+ * @param {number} position The new position for the guild channel
+ * @returns {Promise<GuildChannel>}
+ * @example
+ * // set a new channel position
+ * channel.setPosition(2)
+ * .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`))
+ * .catch(console.error);
+ */
+ setPosition(position) {
+ return this.client.rest.methods.updateChannel(this, { position });
+ }
+
+ /**
+ * Set a new topic for the guild channel
+ * @param {string} topic The new topic for the guild channel
+ * @returns {Promise<GuildChannel>}
+ * @example
+ * // set a new channel topic
+ * channel.setTopic('needs more rate limiting')
+ * .then(newChannel => console.log(`Channel's new topic is ${newChannel.topic}`))
+ * .catch(console.error);
+ */
+ setTopic(topic) {
+ return this.client.rest.methods.updateChannel(this, { topic });
+ }
+
+ /**
+ * Options given when creating a guild channel invite
+ * @typedef {Object} InviteOptions
+ * @property {boolean} [temporary=false] Whether the invite should kick users after 24hrs if they are not given a role
+ * @property {number} [maxAge=0] Time in seconds the invite expires in
+ * @property {number} [maxUses=0] Maximum amount of uses for this invite
+ */
+
+ /**
+ * Create an invite to this guild channel
+ * @param {InviteOptions} [options={}] The options for the invite
+ * @returns {Promise<Invite>}
+ */
+ createInvite(options = {}) {
+ return this.client.rest.methods.createChannelInvite(this, options);
+ }
+
+ /**
+ * Clone this channel
+ * @param {string} [name=this.name] Optional name for the new channel, otherwise it has the name of this channel
+ * @param {boolean} [withPermissions=true] Whether to clone the channel with this channel's permission overwrites
+ * @returns {Promise<GuildChannel>}
+ */
+ clone(name = this.name, withPermissions = true) {
+ return this.guild.createChannel(name, this.type, withPermissions ? this.permissionOverwrites : []);
+ }
+
+ /**
+ * Checks if this channel has the same type, topic, position, name, overwrites and ID as another channel.
+ * In most cases, a simple `channel.id === channel2.id` will do, and is much faster too.
+ * @param {GuildChannel} channel Channel to compare with
+ * @returns {boolean}
+ */
+ equals(channel) {
+ let equal = channel &&
+ this.id === channel.id &&
+ this.type === channel.type &&
+ this.topic === channel.topic &&
+ this.position === channel.position &&
+ this.name === channel.name;
+
+ if (equal) {
+ if (this.permissionOverwrites && channel.permissionOverwrites) {
+ equal = this.permissionOverwrites.equals(channel.permissionOverwrites);
+ } else {
+ equal = !this.permissionOverwrites && !channel.permissionOverwrites;
+ }
+ }
+
+ return equal;
+ }
+
+ /**
+ * When concatenated with a string, this automatically returns the channel's mention instead of the Channel object.
+ * @returns {string}
+ * @example
+ * // Outputs: Hello from #general
+ * console.log(`Hello from ${channel}`);
+ * @example
+ * // Outputs: Hello from #general
+ * console.log('Hello from ' + channel);
+ */
+ toString() {
+ return `<#${this.id}>`;
+ }
+}
+
+module.exports = GuildChannel;
diff --git a/node_modules/discord.js/src/structures/GuildMember.js b/node_modules/discord.js/src/structures/GuildMember.js
new file mode 100644
index 0000000..60a498a
--- /dev/null
+++ b/node_modules/discord.js/src/structures/GuildMember.js
@@ -0,0 +1,442 @@
+const TextBasedChannel = require('./interface/TextBasedChannel');
+const Role = require('./Role');
+const EvaluatedPermissions = require('./EvaluatedPermissions');
+const Constants = require('../util/Constants');
+const Collection = require('../util/Collection');
+const Presence = require('./Presence').Presence;
+
+/**
+ * Represents a member of a guild on Discord
+ * @implements {TextBasedChannel}
+ */
+class GuildMember {
+ constructor(guild, data) {
+ /**
+ * The Client that instantiated this GuildMember
+ * @name GuildMember#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: guild.client });
+
+ /**
+ * The guild that this member is part of
+ * @type {Guild}
+ */
+ this.guild = guild;
+
+ /**
+ * The user that this guild member instance Represents
+ * @type {User}
+ */
+ this.user = {};
+
+ this._roles = [];
+ if (data) this.setup(data);
+
+ /**
+ * The ID of the last message sent by the member in their guild, if one was sent.
+ * @type {?string}
+ */
+ this.lastMessageID = null;
+ }
+
+ setup(data) {
+ /**
+ * Whether this member is deafened server-wide
+ * @type {boolean}
+ */
+ this.serverDeaf = data.deaf;
+
+ /**
+ * Whether this member is muted server-wide
+ * @type {boolean}
+ */
+ this.serverMute = data.mute;
+
+ /**
+ * Whether this member is self-muted
+ * @type {boolean}
+ */
+ this.selfMute = data.self_mute;
+
+ /**
+ * Whether this member is self-deafened
+ * @type {boolean}
+ */
+ this.selfDeaf = data.self_deaf;
+
+ /**
+ * The voice session ID of this member, if any
+ * @type {?string}
+ */
+ this.voiceSessionID = data.session_id;
+
+ /**
+ * The voice channel ID of this member, if any
+ * @type {?string}
+ */
+ this.voiceChannelID = data.channel_id;
+
+ /**
+ * Whether this member is speaking
+ * @type {boolean}
+ */
+ this.speaking = false;
+
+ /**
+ * The nickname of this guild member, if they have one
+ * @type {?string}
+ */
+ this.nickname = data.nick || null;
+
+ /**
+ * The timestamp the member joined the guild at
+ * @type {number}
+ */
+ this.joinedTimestamp = new Date(data.joined_at).getTime();
+
+ this.user = data.user;
+ this._roles = data.roles;
+ }
+
+ /**
+ * The time the member joined the guild
+ * @type {Date}
+ * @readonly
+ */
+ get joinedAt() {
+ return new Date(this.joinedTimestamp);
+ }
+
+ /**
+ * The presence of this guild member
+ * @type {Presence}
+ * @readonly
+ */
+ get presence() {
+ return this.frozenPresence || this.guild.presences.get(this.id) || new Presence();
+ }
+
+ /**
+ * A list of roles that are applied to this GuildMember, mapped by the role ID.
+ * @type {Collection<string, Role>}
+ * @readonly
+ */
+ get roles() {
+ const list = new Collection();
+ const everyoneRole = this.guild.roles.get(this.guild.id);
+
+ if (everyoneRole) list.set(everyoneRole.id, everyoneRole);
+
+ for (const roleID of this._roles) {
+ const role = this.guild.roles.get(roleID);
+ if (role) list.set(role.id, role);
+ }
+
+ return list;
+ }
+
+ /**
+ * The role of the member with the highest position.
+ * @type {Role}
+ * @readonly
+ */
+ get highestRole() {
+ return this.roles.reduce((prev, role) => !prev || role.comparePositionTo(prev) > 0 ? role : prev);
+ }
+
+ /**
+ * Whether this member is muted in any way
+ * @type {boolean}
+ * @readonly
+ */
+ get mute() {
+ return this.selfMute || this.serverMute;
+ }
+
+ /**
+ * Whether this member is deafened in any way
+ * @type {boolean}
+ * @readonly
+ */
+ get deaf() {
+ return this.selfDeaf || this.serverDeaf;
+ }
+
+ /**
+ * The voice channel this member is in, if any
+ * @type {?VoiceChannel}
+ * @readonly
+ */
+ get voiceChannel() {
+ return this.guild.channels.get(this.voiceChannelID);
+ }
+
+ /**
+ * The ID of this user
+ * @type {string}
+ * @readonly
+ */
+ get id() {
+ return this.user.id;
+ }
+
+ /**
+ * The nickname of the member, or their username if they don't have one
+ * @type {string}
+ * @readonly
+ */
+ get displayName() {
+ return this.nickname || this.user.username;
+ }
+
+ /**
+ * The overall set of permissions for the guild member, taking only roles into account
+ * @type {EvaluatedPermissions}
+ * @readonly
+ */
+ get permissions() {
+ if (this.user.id === this.guild.ownerID) return new EvaluatedPermissions(this, Constants.ALL_PERMISSIONS);
+
+ let permissions = 0;
+ const roles = this.roles;
+ for (const role of roles.values()) permissions |= role.permissions;
+
+ const admin = Boolean(permissions & Constants.PermissionFlags.ADMINISTRATOR);
+ if (admin) permissions = Constants.ALL_PERMISSIONS;
+
+ return new EvaluatedPermissions(this, permissions);
+ }
+
+ /**
+ * Whether the member is kickable by the client user.
+ * @type {boolean}
+ * @readonly
+ */
+ get kickable() {
+ if (this.user.id === this.guild.ownerID) return false;
+ if (this.user.id === this.client.user.id) return false;
+ const clientMember = this.guild.member(this.client.user);
+ if (!clientMember.hasPermission(Constants.PermissionFlags.KICK_MEMBERS)) return false;
+ return clientMember.highestRole.comparePositionTo(this.highestRole) > 0;
+ }
+
+ /**
+ * Whether the member is bannable by the client user.
+ * @type {boolean}
+ * @readonly
+ */
+ get bannable() {
+ if (this.user.id === this.guild.ownerID) return false;
+ if (this.user.id === this.client.user.id) return false;
+ const clientMember = this.guild.member(this.client.user);
+ if (!clientMember.hasPermission(Constants.PermissionFlags.BAN_MEMBERS)) return false;
+ return clientMember.highestRole.comparePositionTo(this.highestRole) > 0;
+ }
+
+ /**
+ * Returns `channel.permissionsFor(guildMember)`. Returns evaluated permissions for a member in a guild channel.
+ * @param {ChannelResolvable} channel Guild channel to use as context
+ * @returns {?EvaluatedPermissions}
+ */
+ permissionsIn(channel) {
+ channel = this.client.resolver.resolveChannel(channel);
+ if (!channel || !channel.guild) throw new Error('Could not resolve channel to a guild channel.');
+ return channel.permissionsFor(this);
+ }
+
+ /**
+ * Checks if any of the member's roles have a permission.
+ * @param {PermissionResolvable} permission The permission to check for
+ * @param {boolean} [explicit=false] Whether to require the roles to explicitly have the exact permission
+ * @returns {boolean}
+ */
+ hasPermission(permission, explicit = false) {
+ if (!explicit && this.user.id === this.guild.ownerID) return true;
+ return this.roles.some(r => r.hasPermission(permission, explicit));
+ }
+
+ /**
+ * Checks whether the roles of the member allows them to perform specific actions.
+ * @param {PermissionResolvable[]} permissions The permissions to check for
+ * @param {boolean} [explicit=false] Whether to require the member to explicitly have the exact permissions
+ * @returns {boolean}
+ */
+ hasPermissions(permissions, explicit = false) {
+ if (!explicit && this.user.id === this.guild.ownerID) return true;
+ return permissions.every(p => this.hasPermission(p, explicit));
+ }
+
+ /**
+ * Checks whether the roles of the member allows them to perform specific actions, and lists any missing permissions.
+ * @param {PermissionResolvable[]} permissions The permissions to check for
+ * @param {boolean} [explicit=false] Whether to require the member to explicitly have the exact permissions
+ * @returns {PermissionResolvable[]}
+ */
+ missingPermissions(permissions, explicit = false) {
+ return permissions.filter(p => !this.hasPermission(p, explicit));
+ }
+
+ /**
+ * Edit a guild member
+ * @param {GuildmemberEditData} data The data to edit the member with
+ * @returns {Promise<GuildMember>}
+ */
+ edit(data) {
+ return this.client.rest.methods.updateGuildMember(this, data);
+ }
+
+ /**
+ * Mute/unmute a user
+ * @param {boolean} mute Whether or not the member should be muted
+ * @returns {Promise<GuildMember>}
+ */
+ setMute(mute) {
+ return this.edit({ mute });
+ }
+
+ /**
+ * Deafen/undeafen a user
+ * @param {boolean} deaf Whether or not the member should be deafened
+ * @returns {Promise<GuildMember>}
+ */
+ setDeaf(deaf) {
+ return this.edit({ deaf });
+ }
+
+ /**
+ * Moves the guild member to the given channel.
+ * @param {ChannelResolvable} channel The channel to move the member to
+ * @returns {Promise<GuildMember>}
+ */
+ setVoiceChannel(channel) {
+ return this.edit({ channel });
+ }
+
+ /**
+ * Sets the roles applied to the member.
+ * @param {Collection<string, Role>|Role[]|string[]} roles The roles or role IDs to apply
+ * @returns {Promise<GuildMember>}
+ */
+ setRoles(roles) {
+ return this.edit({ roles });
+ }
+
+ /**
+ * Adds a single role to the member.
+ * @param {Role|string} role The role or ID of the role to add
+ * @returns {Promise<GuildMember>}
+ */
+ addRole(role) {
+ if (!(role instanceof Role)) role = this.guild.roles.get(role);
+ return this.client.rest.methods.addMemberRole(this, role);
+ }
+
+ /**
+ * Adds multiple roles to the member.
+ * @param {Collection<string, Role>|Role[]|string[]} roles The roles or role IDs to add
+ * @returns {Promise<GuildMember>}
+ */
+ addRoles(roles) {
+ let allRoles;
+ if (roles instanceof Collection) {
+ allRoles = this._roles.slice();
+ for (const role of roles.values()) allRoles.push(role.id);
+ } else {
+ allRoles = this._roles.concat(roles);
+ }
+ return this.edit({ roles: allRoles });
+ }
+
+ /**
+ * Removes a single role from the member.
+ * @param {Role|string} role The role or ID of the role to remove
+ * @returns {Promise<GuildMember>}
+ */
+ removeRole(role) {
+ if (!(role instanceof Role)) role = this.guild.roles.get(role);
+ return this.client.rest.methods.removeMemberRole(this, role);
+ }
+
+ /**
+ * Removes multiple roles from the member.
+ * @param {Collection<string, Role>|Role[]|string[]} roles The roles or role IDs to remove
+ * @returns {Promise<GuildMember>}
+ */
+ removeRoles(roles) {
+ const allRoles = this._roles.slice();
+ if (roles instanceof Collection) {
+ for (const role of roles.values()) {
+ const index = allRoles.indexOf(role.id);
+ if (index >= 0) allRoles.splice(index, 1);
+ }
+ } else {
+ for (const role of roles) {
+ const index = allRoles.indexOf(role instanceof Role ? role.id : role);
+ if (index >= 0) allRoles.splice(index, 1);
+ }
+ }
+ return this.edit({ roles: allRoles });
+ }
+
+ /**
+ * Set the nickname for the guild member
+ * @param {string} nick The nickname for the guild member
+ * @returns {Promise<GuildMember>}
+ */
+ setNickname(nick) {
+ return this.edit({ nick });
+ }
+
+ /**
+ * Deletes any DMs with this guild member
+ * @returns {Promise<DMChannel>}
+ */
+ deleteDM() {
+ return this.client.rest.methods.deleteChannel(this);
+ }
+
+ /**
+ * Kick this member from the guild
+ * @returns {Promise<GuildMember>}
+ */
+ kick() {
+ return this.client.rest.methods.kickGuildMember(this.guild, this);
+ }
+
+ /**
+ * Ban this guild member
+ * @param {number} [deleteDays=0] The amount of days worth of messages from this member that should
+ * also be deleted. Between `0` and `7`.
+ * @returns {Promise<GuildMember>}
+ * @example
+ * // ban a guild member
+ * guildMember.ban(7);
+ */
+ ban(deleteDays = 0) {
+ return this.client.rest.methods.banGuildMember(this.guild, this, deleteDays);
+ }
+
+ /**
+ * When concatenated with a string, this automatically concatenates the user's mention instead of the Member object.
+ * @returns {string}
+ * @example
+ * // logs: Hello from <@123456789>!
+ * console.log(`Hello from ${member}!`);
+ */
+ toString() {
+ return `<@${this.nickname ? '!' : ''}${this.user.id}>`;
+ }
+
+ // These are here only for documentation purposes - they are implemented by TextBasedChannel
+ send() { return; }
+ sendMessage() { return; }
+ sendEmbed() { return; }
+ sendFile() { return; }
+ sendCode() { return; }
+}
+
+TextBasedChannel.applyToClass(GuildMember);
+
+module.exports = GuildMember;
diff --git a/node_modules/discord.js/src/structures/Invite.js b/node_modules/discord.js/src/structures/Invite.js
new file mode 100644
index 0000000..b4b34da
--- /dev/null
+++ b/node_modules/discord.js/src/structures/Invite.js
@@ -0,0 +1,159 @@
+const PartialGuild = require('./PartialGuild');
+const PartialGuildChannel = require('./PartialGuildChannel');
+const Constants = require('../util/Constants');
+
+/*
+{ max_age: 86400,
+ code: 'CG9A5',
+ guild:
+ { splash: null,
+ id: '123123123',
+ icon: '123123123',
+ name: 'name' },
+ created_at: '2016-08-28T19:07:04.763368+00:00',
+ temporary: false,
+ uses: 0,
+ max_uses: 0,
+ inviter:
+ { username: '123',
+ discriminator: '4204',
+ bot: true,
+ id: '123123123',
+ avatar: '123123123' },
+ channel: { type: 0, id: '123123', name: 'heavy-testing' } }
+*/
+
+/**
+ * Represents an invitation to a guild channel.
+ * <warn>The only guaranteed properties are `code`, `guild` and `channel`. Other properties can be missing.</warn>
+ */
+class Invite {
+ constructor(client, data) {
+ /**
+ * The client that instantiated the invite
+ * @name Invite#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: client });
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The guild the invite is for. If this guild is already known, this will be a Guild object. If the guild is
+ * unknown, this will be a PartialGuild object.
+ * @type {Guild|PartialGuild}
+ */
+ this.guild = this.client.guilds.get(data.guild.id) || new PartialGuild(this.client, data.guild);
+
+ /**
+ * The code for this invite
+ * @type {string}
+ */
+ this.code = data.code;
+
+ /**
+ * Whether or not this invite is temporary
+ * @type {boolean}
+ */
+ this.temporary = data.temporary;
+
+ /**
+ * The maximum age of the invite, in seconds
+ * @type {?number}
+ */
+ this.maxAge = data.max_age;
+
+ /**
+ * How many times this invite has been used
+ * @type {number}
+ */
+ this.uses = data.uses;
+
+ /**
+ * The maximum uses of this invite
+ * @type {number}
+ */
+ this.maxUses = data.max_uses;
+
+ if (data.inviter) {
+ /**
+ * The user who created this invite
+ * @type {User}
+ */
+ this.inviter = this.client.dataManager.newUser(data.inviter);
+ }
+
+ /**
+ * The channel the invite is for. If this channel is already known, this will be a GuildChannel object.
+ * If the channel is unknown, this will be a PartialGuildChannel object.
+ * @type {GuildChannel|PartialGuildChannel}
+ */
+ this.channel = this.client.channels.get(data.channel.id) || new PartialGuildChannel(this.client, data.channel);
+
+ /**
+ * The timestamp the invite was created at
+ * @type {number}
+ */
+ this.createdTimestamp = new Date(data.created_at).getTime();
+ }
+
+ /**
+ * The time the invite was created
+ * @type {Date}
+ * @readonly
+ */
+ get createdAt() {
+ return new Date(this.createdTimestamp);
+ }
+
+ /**
+ * The timestamp the invite will expire at
+ * @type {number}
+ * @readonly
+ */
+ get expiresTimestamp() {
+ return this.createdTimestamp + (this.maxAge * 1000);
+ }
+
+ /**
+ * The time the invite will expire
+ * @type {Date}
+ * @readonly
+ */
+ get expiresAt() {
+ return new Date(this.expiresTimestamp);
+ }
+
+ /**
+ * The URL to the invite
+ * @type {string}
+ * @readonly
+ */
+ get url() {
+ return Constants.Endpoints.inviteLink(this.code);
+ }
+
+ /**
+ * Deletes this invite
+ * @returns {Promise<Invite>}
+ */
+ delete() {
+ return this.client.rest.methods.deleteInvite(this);
+ }
+
+ /**
+ * When concatenated with a string, this automatically concatenates the invite's URL instead of the object.
+ * @returns {string}
+ * @example
+ * // logs: Invite: https://discord.gg/A1b2C3
+ * console.log(`Invite: ${invite}`);
+ */
+ toString() {
+ return this.url;
+ }
+}
+
+module.exports = Invite;
diff --git a/node_modules/discord.js/src/structures/Message.js b/node_modules/discord.js/src/structures/Message.js
new file mode 100644
index 0000000..7fcc5b4
--- /dev/null
+++ b/node_modules/discord.js/src/structures/Message.js
@@ -0,0 +1,568 @@
+const Attachment = require('./MessageAttachment');
+const Embed = require('./MessageEmbed');
+const MessageReaction = require('./MessageReaction');
+const Collection = require('../util/Collection');
+const Constants = require('../util/Constants');
+const escapeMarkdown = require('../util/EscapeMarkdown');
+
+// Done purely for GuildMember, which would cause a bad circular dependency
+const Discord = require('..');
+
+/**
+ * Represents a message on Discord
+ */
+class Message {
+ constructor(channel, data, client) {
+ /**
+ * The Client that instantiated the Message
+ * @name Message#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: client });
+
+ /**
+ * The channel that the message was sent in
+ * @type {TextChannel|DMChannel|GroupDMChannel}
+ */
+ this.channel = channel;
+
+ if (data) this.setup(data);
+ }
+
+ setup(data) { // eslint-disable-line complexity
+ /**
+ * The ID of the message (unique in the channel it was sent)
+ * @type {string}
+ */
+ this.id = data.id;
+
+ /**
+ * The type of the message
+ * @type {string}
+ */
+ this.type = Constants.MessageTypes[data.type];
+
+ /**
+ * The content of the message
+ * @type {string}
+ */
+ this.content = data.content;
+
+ /**
+ * The author of the message
+ * @type {User}
+ */
+ this.author = this.client.dataManager.newUser(data.author);
+
+ /**
+ * Represents the author of the message as a guild member. Only available if the message comes from a guild
+ * where the author is still a member.
+ * @type {GuildMember}
+ */
+ this.member = this.guild ? this.guild.member(this.author) || null : null;
+
+ /**
+ * Whether or not this message is pinned
+ * @type {boolean}
+ */
+ this.pinned = data.pinned;
+
+ /**
+ * Whether or not the message was Text-To-Speech
+ * @type {boolean}
+ */
+ this.tts = data.tts;
+
+ /**
+ * A random number used for checking message delivery
+ * @type {string}
+ */
+ this.nonce = data.nonce;
+
+ /**
+ * Whether or not this message was sent by Discord, not actually a user (e.g. pin notifications)
+ * @type {boolean}
+ */
+ this.system = data.type === 6;
+
+ /**
+ * A list of embeds in the message - e.g. YouTube Player
+ * @type {MessageEmbed[]}
+ */
+ this.embeds = data.embeds.map(e => new Embed(this, e));
+
+ /**
+ * A collection of attachments in the message - e.g. Pictures - mapped by their ID.
+ * @type {Collection<string, MessageAttachment>}
+ */
+ this.attachments = new Collection();
+ for (const attachment of data.attachments) this.attachments.set(attachment.id, new Attachment(this, attachment));
+
+ /**
+ * The timestamp the message was sent at
+ * @type {number}
+ */
+ this.createdTimestamp = new Date(data.timestamp).getTime();
+
+ /**
+ * The timestamp the message was last edited at (if applicable)
+ * @type {?number}
+ */
+ this.editedTimestamp = data.edited_timestamp ? new Date(data.edited_timestamp).getTime() : null;
+
+ /**
+ * An object containing a further users, roles or channels collections
+ * @type {Object}
+ * @property {Collection<string, User>} mentions.users Mentioned users, maps their ID to the user object.
+ * @property {Collection<string, Role>} mentions.roles Mentioned roles, maps their ID to the role object.
+ * @property {Collection<string, GuildChannel>} mentions.channels Mentioned channels,
+ * maps their ID to the channel object.
+ * @property {boolean} mentions.everyone Whether or not @everyone was mentioned.
+ */
+ this.mentions = {
+ users: new Collection(),
+ roles: new Collection(),
+ channels: new Collection(),
+ everyone: data.mention_everyone,
+ };
+
+ for (const mention of data.mentions) {
+ let user = this.client.users.get(mention.id);
+ if (user) {
+ this.mentions.users.set(user.id, user);
+ } else {
+ user = this.client.dataManager.newUser(mention);
+ this.mentions.users.set(user.id, user);
+ }
+ }
+
+ if (data.mention_roles) {
+ for (const mention of data.mention_roles) {
+ const role = this.channel.guild.roles.get(mention);
+ if (role) this.mentions.roles.set(role.id, role);
+ }
+ }
+
+ if (this.channel.guild) {
+ const channMentionsRaw = data.content.match(/<#([0-9]{14,20})>/g) || [];
+ for (const raw of channMentionsRaw) {
+ const chan = this.channel.guild.channels.get(raw.match(/([0-9]{14,20})/g)[0]);
+ if (chan) this.mentions.channels.set(chan.id, chan);
+ }
+ }
+
+ this._edits = [];
+
+ /**
+ * A collection of reactions to this message, mapped by the reaction "id".
+ * @type {Collection<string, MessageReaction>}
+ */
+ this.reactions = new Collection();
+
+ if (data.reactions && data.reactions.length > 0) {
+ for (const reaction of data.reactions) {
+ const id = reaction.emoji.id ? `${reaction.emoji.name}:${reaction.emoji.id}` : reaction.emoji.name;
+ this.reactions.set(id, new MessageReaction(this, reaction.emoji, reaction.count, reaction.me));
+ }
+ }
+
+ /**
+ * ID of the webhook that sent the message, if applicable
+ * @type {?string}
+ */
+ this.webhookID = data.webhook_id || null;
+ }
+
+ patch(data) { // eslint-disable-line complexity
+ if (data.author) {
+ this.author = this.client.users.get(data.author.id);
+ if (this.guild) this.member = this.guild.member(this.author);
+ }
+ if (data.content) this.content = data.content;
+ if (data.timestamp) this.createdTimestamp = new Date(data.timestamp).getTime();
+ if (data.edited_timestamp) {
+ this.editedTimestamp = data.edited_timestamp ? new Date(data.edited_timestamp).getTime() : null;
+ }
+ if ('tts' in data) this.tts = data.tts;
+ if ('mention_everyone' in data) this.mentions.everyone = data.mention_everyone;
+ if (data.nonce) this.nonce = data.nonce;
+ if (data.embeds) this.embeds = data.embeds.map(e => new Embed(this, e));
+ if (data.type > -1) {
+ this.system = false;
+ if (data.type === 6) this.system = true;
+ }
+ if (data.attachments) {
+ this.attachments = new Collection();
+ for (const attachment of data.attachments) {
+ this.attachments.set(attachment.id, new Attachment(this, attachment));
+ }
+ }
+ if (data.mentions) {
+ for (const mention of data.mentions) {
+ let user = this.client.users.get(mention.id);
+ if (user) {
+ this.mentions.users.set(user.id, user);
+ } else {
+ user = this.client.dataManager.newUser(mention);
+ this.mentions.users.set(user.id, user);
+ }
+ }
+ }
+ if (data.mention_roles) {
+ for (const mention of data.mention_roles) {
+ const role = this.channel.guild.roles.get(mention);
+ if (role) this.mentions.roles.set(role.id, role);
+ }
+ }
+ if (data.id) this.id = data.id;
+ if (this.channel.guild && data.content) {
+ const channMentionsRaw = data.content.match(/<#([0-9]{14,20})>/g) || [];
+ for (const raw of channMentionsRaw) {
+ const chan = this.channel.guild.channels.get(raw.match(/([0-9]{14,20})/g)[0]);
+ if (chan) this.mentions.channels.set(chan.id, chan);
+ }
+ }
+ if (data.reactions) {
+ this.reactions = new Collection();
+ if (data.reactions.length > 0) {
+ for (const reaction of data.reactions) {
+ const id = reaction.emoji.id ? `${reaction.emoji.name}:${reaction.emoji.id}` : reaction.emoji.name;
+ this.reactions.set(id, new MessageReaction(this, data.emoji, data.count, data.me));
+ }
+ }
+ }
+ }
+
+ /**
+ * The time the message was sent
+ * @type {Date}
+ * @readonly
+ */
+ get createdAt() {
+ return new Date(this.createdTimestamp);
+ }
+
+ /**
+ * The time the message was last edited at (if applicable)
+ * @type {?Date}
+ * @readonly
+ */
+ get editedAt() {
+ return this.editedTimestamp ? new Date(this.editedTimestamp) : null;
+ }
+
+ /**
+ * The guild the message was sent in (if in a guild channel)
+ * @type {?Guild}
+ * @readonly
+ */
+ get guild() {
+ return this.channel.guild || null;
+ }
+
+ /**
+ * The message contents with all mentions replaced by the equivalent text. If mentions cannot be resolved to a name,
+ * the relevant mention in the message content will not be converted.
+ * @type {string}
+ * @readonly
+ */
+ get cleanContent() {
+ return this.content
+ .replace(/@(everyone|here)/g, '@\u200b$1')
+ .replace(/<@!?[0-9]+>/g, (input) => {
+ const id = input.replace(/<|!|>|@/g, '');
+ if (this.channel.type === 'dm' || this.channel.type === 'group') {
+ return this.client.users.has(id) ? `@${this.client.users.get(id).username}` : input;
+ }
+
+ const member = this.channel.guild.members.get(id);
+ if (member) {
+ if (member.nickname) return `@${member.nickname}`;
+ return `@${member.user.username}`;
+ } else {
+ const user = this.client.users.get(id);
+ if (user) return `@${user.username}`;
+ return input;
+ }
+ })
+ .replace(/<#[0-9]+>/g, (input) => {
+ const channel = this.client.channels.get(input.replace(/<|#|>/g, ''));
+ if (channel) return `#${channel.name}`;
+ return input;
+ })
+ .replace(/<@&[0-9]+>/g, (input) => {
+ if (this.channel.type === 'dm' || this.channel.type === 'group') return input;
+ const role = this.guild.roles.get(input.replace(/<|@|>|&/g, ''));
+ if (role) return `@${role.name}`;
+ return input;
+ });
+ }
+
+ /**
+ * An array of cached versions of the message, including the current version.
+ * Sorted from latest (first) to oldest (last).
+ * @type {Message[]}
+ * @readonly
+ */
+ get edits() {
+ const copy = this._edits.slice();
+ copy.unshift(this);
+ return copy;
+ }
+
+ /**
+ * Whether the message is editable by the client user.
+ * @type {boolean}
+ * @readonly
+ */
+ get editable() {
+ return this.author.id === this.client.user.id;
+ }
+
+ /**
+ * Whether the message is deletable by the client user.
+ * @type {boolean}
+ * @readonly
+ */
+ get deletable() {
+ return this.author.id === this.client.user.id || (this.guild &&
+ this.channel.permissionsFor(this.client.user).hasPermission(Constants.PermissionFlags.MANAGE_MESSAGES)
+ );
+ }
+
+ /**
+ * Whether the message is pinnable by the client user.
+ * @type {boolean}
+ * @readonly
+ */
+ get pinnable() {
+ return !this.guild ||
+ this.channel.permissionsFor(this.client.user).hasPermission(Constants.PermissionFlags.MANAGE_MESSAGES);
+ }
+
+ /**
+ * Whether or not a user, channel or role is mentioned in this message.
+ * @param {GuildChannel|User|Role|string} data either a guild channel, user or a role object, or a string representing
+ * the ID of any of these.
+ * @returns {boolean}
+ */
+ isMentioned(data) {
+ data = data && data.id ? data.id : data;
+ return this.mentions.users.has(data) || this.mentions.channels.has(data) || this.mentions.roles.has(data);
+ }
+
+ /**
+ * Whether or not a guild member is mentioned in this message. Takes into account
+ * user mentions, role mentions, and @everyone/@here mentions.
+ * @param {GuildMember|User} member Member/user to check for a mention of
+ * @returns {boolean}
+ */
+ isMemberMentioned(member) {
+ if (this.mentions.everyone) return true;
+ if (this.mentions.users.has(member.id)) return true;
+ if (member instanceof Discord.GuildMember && member.roles.some(r => this.mentions.roles.has(r.id))) return true;
+ return false;
+ }
+
+ /**
+ * Options that can be passed into editMessage
+ * @typedef {Object} MessageEditOptions
+ * @property {Object} [embed] An embed to be added/edited
+ * @property {string|boolean} [code] Language for optional codeblock formatting to apply
+ */
+
+ /**
+ * Edit the content of the message
+ * @param {StringResolvable} [content] The new content for the message
+ * @param {MessageEditOptions} [options] The options to provide
+ * @returns {Promise<Message>}
+ * @example
+ * // update the content of a message
+ * message.edit('This is my new content!')
+ * .then(msg => console.log(`Updated the content of a message from ${msg.author}`))
+ * .catch(console.error);
+ */
+ edit(content, options) {
+ if (!options && typeof content === 'object') {
+ options = content;
+ content = '';
+ } else if (!options) {
+ options = {};
+ }
+ return this.client.rest.methods.updateMessage(this, content, options);
+ }
+
+ /**
+ * Edit the content of the message, with a code block
+ * @param {string} lang Language for the code block
+ * @param {StringResolvable} content The new content for the message
+ * @returns {Promise<Message>}
+ */
+ editCode(lang, content) {
+ content = escapeMarkdown(this.client.resolver.resolveString(content), true);
+ return this.edit(`\`\`\`${lang || ''}\n${content}\n\`\`\``);
+ }
+
+ /**
+ * Pins this message to the channel's pinned messages
+ * @returns {Promise<Message>}
+ */
+ pin() {
+ return this.client.rest.methods.pinMessage(this);
+ }
+
+ /**
+ * Unpins this message from the channel's pinned messages
+ * @returns {Promise<Message>}
+ */
+ unpin() {
+ return this.client.rest.methods.unpinMessage(this);
+ }
+
+ /**
+ * Add a reaction to the message
+ * @param {string|Emoji|ReactionEmoji} emoji Emoji to react with
+ * @returns {Promise<MessageReaction>}
+ */
+ react(emoji) {
+ emoji = this.client.resolver.resolveEmojiIdentifier(emoji);
+ if (!emoji) throw new TypeError('Emoji must be a string or Emoji/ReactionEmoji');
+
+ return this.client.rest.methods.addMessageReaction(this, emoji);
+ }
+
+ /**
+ * Remove all reactions from a message
+ * @returns {Promise<Message>}
+ */
+ clearReactions() {
+ return this.client.rest.methods.removeMessageReactions(this);
+ }
+
+ /**
+ * Deletes the message
+ * @param {number} [timeout=0] How long to wait to delete the message in milliseconds
+ * @returns {Promise<Message>}
+ * @example
+ * // delete a message
+ * message.delete()
+ * .then(msg => console.log(`Deleted message from ${msg.author}`))
+ * .catch(console.error);
+ */
+ delete(timeout = 0) {
+ if (timeout <= 0) {
+ return this.client.rest.methods.deleteMessage(this);
+ } else {
+ return new Promise(resolve => {
+ this.client.setTimeout(() => {
+ resolve(this.delete());
+ }, timeout);
+ });
+ }
+ }
+
+ /**
+ * Reply to the message
+ * @param {StringResolvable} content The content for the message
+ * @param {MessageOptions} [options = {}] The options to provide
+ * @returns {Promise<Message|Message[]>}
+ * @example
+ * // reply to a message
+ * message.reply('Hey, I\'m a reply!')
+ * .then(msg => console.log(`Sent a reply to ${msg.author}`))
+ * .catch(console.error);
+ */
+ reply(content, options = {}) {
+ content = `${this.guild || this.channel.type === 'group' ? `${this.author}, ` : ''}${content}`;
+ return this.channel.send(content, options);
+ }
+
+ /**
+ * Fetches the webhook used to create this message.
+ * @returns {Promise<?Webhook>}
+ */
+ fetchWebhook() {
+ if (!this.webhookID) return Promise.reject(new Error('The message was not sent by a webhook.'));
+ return this.client.fetchWebhook(this.webhookID);
+ }
+
+ /**
+ * Used mainly internally. Whether two messages are identical in properties. If you want to compare messages
+ * without checking all the properties, use `message.id === message2.id`, which is much more efficient. This
+ * method allows you to see if there are differences in content, embeds, attachments, nonce and tts properties.
+ * @param {Message} message The message to compare it to
+ * @param {Object} rawData Raw data passed through the WebSocket about this message
+ * @returns {boolean}
+ */
+ equals(message, rawData) {
+ if (!message) return false;
+ const embedUpdate = !message.author && !message.attachments;
+ if (embedUpdate) return this.id === message.id && this.embeds.length === message.embeds.length;
+
+ let equal = this.id === message.id &&
+ this.author.id === message.author.id &&
+ this.content === message.content &&
+ this.tts === message.tts &&
+ this.nonce === message.nonce &&
+ this.embeds.length === message.embeds.length &&
+ this.attachments.length === message.attachments.length;
+
+ if (equal && rawData) {
+ equal = this.mentions.everyone === message.mentions.everyone &&
+ this.createdTimestamp === new Date(rawData.timestamp).getTime() &&
+ this.editedTimestamp === new Date(rawData.edited_timestamp).getTime();
+ }
+
+ return equal;
+ }
+
+ /**
+ * When concatenated with a string, this automatically concatenates the message's content instead of the object.
+ * @returns {string}
+ * @example
+ * // logs: Message: This is a message!
+ * console.log(`Message: ${message}`);
+ */
+ toString() {
+ return this.content;
+ }
+
+ _addReaction(emoji, user) {
+ const emojiID = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name;
+ let reaction;
+ if (this.reactions.has(emojiID)) {
+ reaction = this.reactions.get(emojiID);
+ if (!reaction.me) reaction.me = user.id === this.client.user.id;
+ } else {
+ reaction = new MessageReaction(this, emoji, 0, user.id === this.client.user.id);
+ this.reactions.set(emojiID, reaction);
+ }
+ if (!reaction.users.has(user.id)) {
+ reaction.users.set(user.id, user);
+ reaction.count++;
+ return reaction;
+ }
+ return null;
+ }
+
+ _removeReaction(emoji, user) {
+ const emojiID = emoji.id || emoji;
+ if (this.reactions.has(emojiID)) {
+ const reaction = this.reactions.get(emojiID);
+ if (reaction.users.has(user.id)) {
+ reaction.users.delete(user.id);
+ reaction.count--;
+ if (user.id === this.client.user.id) reaction.me = false;
+ return reaction;
+ }
+ }
+ return null;
+ }
+
+ _clearReactions() {
+ this.reactions.clear();
+ }
+}
+
+module.exports = Message;
diff --git a/node_modules/discord.js/src/structures/MessageAttachment.js b/node_modules/discord.js/src/structures/MessageAttachment.js
new file mode 100644
index 0000000..29dfb52
--- /dev/null
+++ b/node_modules/discord.js/src/structures/MessageAttachment.js
@@ -0,0 +1,68 @@
+/**
+ * Represents an attachment in a message
+ */
+class MessageAttachment {
+ constructor(message, data) {
+ /**
+ * The Client that instantiated this MessageAttachment.
+ * @name MessageAttachment#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: message.client });
+
+ /**
+ * The message this attachment is part of.
+ * @type {Message}
+ */
+ this.message = message;
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The ID of this attachment
+ * @type {string}
+ */
+ this.id = data.id;
+
+ /**
+ * The file name of this attachment
+ * @type {string}
+ */
+ this.filename = data.filename;
+
+ /**
+ * The size of this attachment in bytes
+ * @type {number}
+ */
+ this.filesize = data.size;
+
+ /**
+ * The URL to this attachment
+ * @type {string}
+ */
+ this.url = data.url;
+
+ /**
+ * The Proxy URL to this attachment
+ * @type {string}
+ */
+ this.proxyURL = data.proxy_url;
+
+ /**
+ * The height of this attachment (if an image)
+ * @type {?number}
+ */
+ this.height = data.height;
+
+ /**
+ * The width of this attachment (if an image)
+ * @type {?number}
+ */
+ this.width = data.width;
+ }
+}
+
+module.exports = MessageAttachment;
diff --git a/node_modules/discord.js/src/structures/MessageCollector.js b/node_modules/discord.js/src/structures/MessageCollector.js
new file mode 100644
index 0000000..f84ecbd
--- /dev/null
+++ b/node_modules/discord.js/src/structures/MessageCollector.js
@@ -0,0 +1,151 @@
+const EventEmitter = require('events').EventEmitter;
+const Collection = require('../util/Collection');
+
+/**
+ * Collects messages based on a specified filter, then emits them.
+ * @extends {EventEmitter}
+ */
+class MessageCollector extends EventEmitter {
+ /**
+ * A function that takes a Message object and a MessageCollector and returns a boolean.
+ * ```js
+ * function(message, collector) {
+ * if (message.content.includes('discord')) {
+ * return true; // passed the filter test
+ * }
+ * return false; // failed the filter test
+ * }
+ * ```
+ * @typedef {Function} CollectorFilterFunction
+ */
+
+ /**
+ * An object containing options used to configure a MessageCollector. All properties are optional.
+ * @typedef {Object} CollectorOptions
+ * @property {number} [time] Duration for the collector in milliseconds
+ * @property {number} [max] Maximum number of messages to handle
+ * @property {number} [maxMatches] Maximum number of successfully filtered messages to obtain
+ */
+
+ /**
+ * @param {Channel} channel The channel to collect messages in
+ * @param {CollectorFilterFunction} filter The filter function
+ * @param {CollectorOptions} [options] Options for the collector
+ */
+ constructor(channel, filter, options = {}) {
+ super();
+
+ /**
+ * The channel this collector is operating on
+ * @type {Channel}
+ */
+ this.channel = channel;
+
+ /**
+ * A function used to filter messages that the collector collects.
+ * @type {CollectorFilterFunction}
+ */
+ this.filter = filter;
+
+ /**
+ * Options for the collecor.
+ * @type {CollectorOptions}
+ */
+ this.options = options;
+
+ /**
+ * Whether this collector has stopped collecting messages.
+ * @type {boolean}
+ */
+ this.ended = false;
+
+ /**
+ * A collection of collected messages, mapped by message ID.
+ * @type {Collection<string, Message>}
+ */
+ this.collected = new Collection();
+
+ this.listener = message => this.verify(message);
+ this.channel.client.on('message', this.listener);
+ if (options.time) this.channel.client.setTimeout(() => this.stop('time'), options.time);
+ }
+
+ /**
+ * Verifies a message against the filter and options
+ * @private
+ * @param {Message} message The message
+ * @returns {boolean}
+ */
+ verify(message) {
+ if (this.channel ? this.channel.id !== message.channel.id : false) return false;
+ if (this.filter(message, this)) {
+ this.collected.set(message.id, message);
+ /**
+ * Emitted whenever the collector receives a message that passes the filter test.
+ * @param {Message} message The received message
+ * @param {MessageCollector} collector The collector the message passed through
+ * @event MessageCollector#message
+ */
+ this.emit('message', message, this);
+ if (this.collected.size >= this.options.maxMatches) this.stop('matchesLimit');
+ else if (this.options.max && this.collected.size === this.options.max) this.stop('limit');
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns a promise that resolves when a valid message is sent. Rejects
+ * with collected messages if the Collector ends before receiving a message.
+ * @type {Promise<Message>}
+ * @readonly
+ */
+ get next() {
+ return new Promise((resolve, reject) => {
+ if (this.ended) {
+ reject(this.collected);
+ return;
+ }
+
+ const cleanup = () => {
+ this.removeListener('message', onMessage);
+ this.removeListener('end', onEnd);
+ };
+
+ const onMessage = (...args) => {
+ cleanup();
+ resolve(...args);
+ };
+
+ const onEnd = (...args) => {
+ cleanup();
+ reject(...args);
+ };
+
+ this.once('message', onMessage);
+ this.once('end', onEnd);
+ });
+ }
+
+ /**
+ * Stops the collector and emits `end`.
+ * @param {string} [reason='user'] An optional reason for stopping the collector
+ */
+ stop(reason = 'user') {
+ if (this.ended) return;
+ this.ended = true;
+ this.channel.client.removeListener('message', this.listener);
+ /**
+ * Emitted when the Collector stops collecting.
+ * @param {Collection<string, Message>} collection A collection of messages collected
+ * during the lifetime of the collector, mapped by the ID of the messages.
+ * @param {string} reason The reason for the end of the collector. If it ended because it reached the specified time
+ * limit, this would be `time`. If you invoke `.stop()` without specifying a reason, this would be `user`. If it
+ * ended because it reached its message limit, it will be `limit`.
+ * @event MessageCollector#end
+ */
+ this.emit('end', this.collected, reason);
+ }
+}
+
+module.exports = MessageCollector;
diff --git a/node_modules/discord.js/src/structures/MessageEmbed.js b/node_modules/discord.js/src/structures/MessageEmbed.js
new file mode 100644
index 0000000..1249c42
--- /dev/null
+++ b/node_modules/discord.js/src/structures/MessageEmbed.js
@@ -0,0 +1,293 @@
+/**
+ * Represents an embed in a message (image/video preview, rich embed, etc.)
+ */
+class MessageEmbed {
+ constructor(message, data) {
+ /**
+ * The client that instantiated this embed
+ * @name MessageEmbed#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: message.client });
+
+ /**
+ * The message this embed is part of
+ * @type {Message}
+ */
+ this.message = message;
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The type of this embed
+ * @type {string}
+ */
+ this.type = data.type;
+
+ /**
+ * The title of this embed, if there is one
+ * @type {?string}
+ */
+ this.title = data.title;
+
+ /**
+ * The description of this embed, if there is one
+ * @type {?string}
+ */
+ this.description = data.description;
+
+ /**
+ * The URL of this embed
+ * @type {string}
+ */
+ this.url = data.url;
+
+ /**
+ * The color of the embed
+ * @type {number}
+ */
+ this.color = data.color;
+
+ /**
+ * The fields of this embed
+ * @type {MessageEmbedField[]}
+ */
+ this.fields = [];
+ if (data.fields) for (const field of data.fields) this.fields.push(new MessageEmbedField(this, field));
+
+ /**
+ * The timestamp of this embed
+ * @type {number}
+ */
+ this.createdTimestamp = data.timestamp;
+
+ /**
+ * The thumbnail of this embed, if there is one
+ * @type {MessageEmbedThumbnail}
+ */
+ this.thumbnail = data.thumbnail ? new MessageEmbedThumbnail(this, data.thumbnail) : null;
+
+ /**
+ * The author of this embed, if there is one
+ * @type {MessageEmbedAuthor}
+ */
+ this.author = data.author ? new MessageEmbedAuthor(this, data.author) : null;
+
+ /**
+ * The provider of this embed, if there is one
+ * @type {MessageEmbedProvider}
+ */
+ this.provider = data.provider ? new MessageEmbedProvider(this, data.provider) : null;
+
+ /**
+ * The footer of this embed
+ * @type {MessageEmbedFooter}
+ */
+ this.footer = data.footer ? new MessageEmbedFooter(this, data.footer) : null;
+ }
+
+ /**
+ * The date this embed was created
+ * @type {Date}
+ */
+ get createdAt() {
+ return new Date(this.createdTimestamp);
+ }
+
+ /**
+ * The hexadecimal version of the embed color, with a leading hash.
+ * @type {string}
+ * @readonly
+ */
+ get hexColor() {
+ let col = this.color.toString(16);
+ while (col.length < 6) col = `0${col}`;
+ return `#${col}`;
+ }
+}
+
+/**
+ * Represents a thumbnail for a message embed
+ */
+class MessageEmbedThumbnail {
+ constructor(embed, data) {
+ /**
+ * The embed this thumbnail is part of
+ * @type {MessageEmbed}
+ */
+ this.embed = embed;
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The URL for this thumbnail
+ * @type {string}
+ */
+ this.url = data.url;
+
+ /**
+ * The Proxy URL for this thumbnail
+ * @type {string}
+ */
+ this.proxyURL = data.proxy_url;
+
+ /**
+ * The height of the thumbnail
+ * @type {number}
+ */
+ this.height = data.height;
+
+ /**
+ * The width of the thumbnail
+ * @type {number}
+ */
+ this.width = data.width;
+ }
+}
+
+/**
+ * Represents a provider for a message embed
+ */
+class MessageEmbedProvider {
+ constructor(embed, data) {
+ /**
+ * The embed this provider is part of
+ * @type {MessageEmbed}
+ */
+ this.embed = embed;
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The name of this provider
+ * @type {string}
+ */
+ this.name = data.name;
+
+ /**
+ * The URL of this provider
+ * @type {string}
+ */
+ this.url = data.url;
+ }
+}
+
+/**
+ * Represents an author for a message embed
+ */
+class MessageEmbedAuthor {
+ constructor(embed, data) {
+ /**
+ * The embed this author is part of
+ * @type {MessageEmbed}
+ */
+ this.embed = embed;
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The name of this author
+ * @type {string}
+ */
+ this.name = data.name;
+
+ /**
+ * The URL of this author
+ * @type {string}
+ */
+ this.url = data.url;
+
+ /**
+ * The icon URL of this author
+ * @type {string}
+ */
+ this.iconURL = data.icon_url;
+ }
+}
+
+/**
+ * Represents a field for a message embed
+ */
+class MessageEmbedField {
+ constructor(embed, data) {
+ /**
+ * The embed this footer is part of
+ * @type {MessageEmbed}
+ */
+ this.embed = embed;
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The name of this field
+ * @type {string}
+ */
+ this.name = data.name;
+
+ /**
+ * The value of this field
+ * @type {string}
+ */
+ this.value = data.value;
+
+ /**
+ * If this field is displayed inline
+ * @type {boolean}
+ */
+ this.inline = data.inline;
+ }
+}
+
+/**
+ * Represents the footer of a message embed
+ */
+class MessageEmbedFooter {
+ constructor(embed, data) {
+ /**
+ * The embed this footer is part of
+ * @type {MessageEmbed}
+ */
+ this.embed = embed;
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The text in this footer
+ * @type {string}
+ */
+ this.text = data.text;
+
+ /**
+ * The icon URL of this footer
+ * @type {string}
+ */
+ this.iconURL = data.icon_url;
+
+ /**
+ * The proxy icon URL of this footer
+ * @type {string}
+ */
+ this.proxyIconUrl = data.proxy_icon_url;
+ }
+}
+
+MessageEmbed.Thumbnail = MessageEmbedThumbnail;
+MessageEmbed.Provider = MessageEmbedProvider;
+MessageEmbed.Author = MessageEmbedAuthor;
+MessageEmbed.Field = MessageEmbedField;
+MessageEmbed.Footer = MessageEmbedFooter;
+
+module.exports = MessageEmbed;
diff --git a/node_modules/discord.js/src/structures/MessageReaction.js b/node_modules/discord.js/src/structures/MessageReaction.js
new file mode 100644
index 0000000..30c555f
--- /dev/null
+++ b/node_modules/discord.js/src/structures/MessageReaction.js
@@ -0,0 +1,92 @@
+const Collection = require('../util/Collection');
+const Emoji = require('./Emoji');
+const ReactionEmoji = require('./ReactionEmoji');
+
+/**
+ * Represents a reaction to a message
+ */
+class MessageReaction {
+ constructor(message, emoji, count, me) {
+ /**
+ * The message that this reaction refers to
+ * @type {Message}
+ */
+ this.message = message;
+
+ /**
+ * Whether the client has given this reaction
+ * @type {boolean}
+ */
+ this.me = me;
+
+ /**
+ * The number of people that have given the same reaction.
+ * @type {number}
+ */
+ this.count = count || 0;
+
+ /**
+ * The users that have given this reaction, mapped by their ID.
+ * @type {Collection<string, User>}
+ */
+ this.users = new Collection();
+
+ this._emoji = new ReactionEmoji(this, emoji.name, emoji.id);
+ }
+
+ /**
+ * The emoji of this reaction, either an Emoji object for known custom emojis, or a ReactionEmoji
+ * object which has fewer properties. Whatever the prototype of the emoji, it will still have
+ * `name`, `id`, `identifier` and `toString()`
+ * @type {Emoji|ReactionEmoji}
+ */
+ get emoji() {
+ if (this._emoji instanceof Emoji) return this._emoji;
+ // check to see if the emoji has become known to the client
+ if (this._emoji.id) {
+ const emojis = this.message.client.emojis;
+ if (emojis.has(this._emoji.id)) {
+ const emoji = emojis.get(this._emoji.id);
+ this._emoji = emoji;
+ return emoji;
+ }
+ }
+ return this._emoji;
+ }
+
+ /**
+ * Removes a user from this reaction.
+ * @param {UserResolvable} [user=this.message.client.user] User to remove the reaction of
+ * @returns {Promise<MessageReaction>}
+ */
+ remove(user = this.message.client.user) {
+ const message = this.message;
+ user = this.message.client.resolver.resolveUserID(user);
+ if (!user) return Promise.reject('Couldn\'t resolve the user ID to remove from the reaction.');
+ return message.client.rest.methods.removeMessageReaction(
+ message, this.emoji.identifier, user
+ );
+ }
+
+ /**
+ * Fetch all the users that gave this reaction. Resolves with a collection of users, mapped by their IDs.
+ * @param {number} [limit=100] the maximum amount of users to fetch, defaults to 100
+ * @returns {Promise<Collection<string, User>>}
+ */
+ fetchUsers(limit = 100) {
+ const message = this.message;
+ return message.client.rest.methods.getMessageReactionUsers(
+ message, this.emoji.identifier, limit
+ ).then(users => {
+ this.users = new Collection();
+ for (const rawUser of users) {
+ const user = this.message.client.dataManager.newUser(rawUser);
+ this.users.set(user.id, user);
+ }
+ this.count = this.users.size;
+ return users;
+ });
+ }
+}
+
+module.exports = MessageReaction;
diff --git a/node_modules/discord.js/src/structures/OAuth2Application.js b/node_modules/discord.js/src/structures/OAuth2Application.js
new file mode 100644
index 0000000..b7c7285
--- /dev/null
+++ b/node_modules/discord.js/src/structures/OAuth2Application.js
@@ -0,0 +1,82 @@
+/**
+ * Represents an OAuth2 Application
+ */
+class OAuth2Application {
+ constructor(client, data) {
+ /**
+ * The client that instantiated the application
+ * @name OAuth2Application#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: client });
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The ID of the app
+ * @type {string}
+ */
+ this.id = data.id;
+
+ /**
+ * The name of the app
+ * @type {string}
+ */
+ this.name = data.name;
+
+ /**
+ * The app's description
+ * @type {string}
+ */
+ this.description = data.description;
+
+ /**
+ * The app's icon hash
+ * @type {string}
+ */
+ this.icon = data.icon;
+
+ /**
+ * The app's icon URL
+ * @type {string}
+ */
+ this.iconURL = `https://cdn.discordapp.com/app-icons/${this.id}/${this.icon}.jpg`;
+
+ /**
+ * The app's RPC origins
+ * @type {Array<string>}
+ */
+ this.rpcOrigins = data.rpc_origins;
+ }
+
+ /**
+ * The timestamp the app was created at
+ * @type {number}
+ * @readonly
+ */
+ get createdTimestamp() {
+ return (this.id / 4194304) + 1420070400000;
+ }
+
+ /**
+ * The time the app was created
+ * @type {Date}
+ * @readonly
+ */
+ get createdAt() {
+ return new Date(this.createdTimestamp);
+ }
+
+ /**
+ * When concatenated with a string, this automatically concatenates the app name rather than the app object.
+ * @returns {string}
+ */
+ toString() {
+ return this.name;
+ }
+}
+
+module.exports = OAuth2Application;
diff --git a/node_modules/discord.js/src/structures/PartialGuild.js b/node_modules/discord.js/src/structures/PartialGuild.js
new file mode 100644
index 0000000..407212e
--- /dev/null
+++ b/node_modules/discord.js/src/structures/PartialGuild.js
@@ -0,0 +1,51 @@
+/*
+{ splash: null,
+ id: '123123123',
+ icon: '123123123',
+ name: 'name' }
+*/
+
+/**
+ * Represents a guild that the client only has limited information for - e.g. from invites.
+ */
+class PartialGuild {
+ constructor(client, data) {
+ /**
+ * The Client that instantiated this PartialGuild
+ * @name PartialGuild#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: client });
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The ID of this guild
+ * @type {string}
+ */
+ this.id = data.id;
+
+ /**
+ * The name of this guild
+ * @type {string}
+ */
+ this.name = data.name;
+
+ /**
+ * The hash of this guild's icon, or null if there is none.
+ * @type {?string}
+ */
+ this.icon = data.icon;
+
+ /**
+ * The hash of the guild splash image, or null if no splash (VIP only)
+ * @type {?string}
+ */
+ this.splash = data.splash;
+ }
+}
+
+module.exports = PartialGuild;
diff --git a/node_modules/discord.js/src/structures/PartialGuildChannel.js b/node_modules/discord.js/src/structures/PartialGuildChannel.js
new file mode 100644
index 0000000..e58a6bb
--- /dev/null
+++ b/node_modules/discord.js/src/structures/PartialGuildChannel.js
@@ -0,0 +1,44 @@
+const Constants = require('../util/Constants');
+
+/*
+{ type: 0, id: '123123', name: 'heavy-testing' } }
+*/
+
+/**
+ * Represents a guild channel that the client only has limited information for - e.g. from invites.
+ */
+class PartialGuildChannel {
+ constructor(client, data) {
+ /**
+ * The Client that instantiated this PartialGuildChannel
+ * @name PartialGuildChannel#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: client });
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The ID of this guild channel
+ * @type {string}
+ */
+ this.id = data.id;
+
+ /**
+ * The name of this guild channel
+ * @type {string}
+ */
+ this.name = data.name;
+
+ /**
+ * The type of this guild channel - `text` or `voice`
+ * @type {string}
+ */
+ this.type = Constants.ChannelTypes.text === data.type ? 'text' : 'voice';
+ }
+}
+
+module.exports = PartialGuildChannel;
diff --git a/node_modules/discord.js/src/structures/PermissionOverwrites.js b/node_modules/discord.js/src/structures/PermissionOverwrites.js
new file mode 100644
index 0000000..9b2f536
--- /dev/null
+++ b/node_modules/discord.js/src/structures/PermissionOverwrites.js
@@ -0,0 +1,43 @@
+/**
+ * Represents a permission overwrite for a role or member in a guild channel.
+ */
+class PermissionOverwrites {
+ constructor(guildChannel, data) {
+ /**
+ * The GuildChannel this overwrite is for
+ * @name PermissionOverwrites#channel
+ * @type {GuildChannel}
+ * @readonly
+ */
+ Object.defineProperty(this, 'channel', { value: guildChannel });
+
+ if (data) this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The ID of this overwrite, either a user ID or a role ID
+ * @type {string}
+ */
+ this.id = data.id;
+
+ /**
+ * The type of this overwrite
+ * @type {string}
+ */
+ this.type = data.type;
+
+ this.deny = data.deny;
+ this.allow = data.allow;
+ }
+
+ /**
+ * Delete this Permission Overwrite.
+ * @returns {Promise<PermissionOverwrites>}
+ */
+ delete() {
+ return this.channel.client.rest.methods.deletePermissionOverwrites(this);
+ }
+}
+
+module.exports = PermissionOverwrites;
diff --git a/node_modules/discord.js/src/structures/Presence.js b/node_modules/discord.js/src/structures/Presence.js
new file mode 100644
index 0000000..ddca5fb
--- /dev/null
+++ b/node_modules/discord.js/src/structures/Presence.js
@@ -0,0 +1,92 @@
+/**
+ * Represents a user's presence
+ */
+class Presence {
+ constructor(data = {}) {
+ /**
+ * The status of the presence:
+ *
+ * * **`online`** - user is online
+ * * **`offline`** - user is offline or invisible
+ * * **`idle`** - user is AFK
+ * * **`dnd`** - user is in Do not Disturb
+ * @type {string}
+ */
+ this.status = data.status || 'offline';
+
+ /**
+ * The game that the user is playing, `null` if they aren't playing a game.
+ * @type {?Game}
+ */
+ this.game = data.game ? new Game(data.game) : null;
+ }
+
+ update(data) {
+ this.status = data.status || this.status;
+ this.game = data.game ? new Game(data.game) : null;
+ }
+
+ /**
+ * Whether this presence is equal to another
+ * @param {Presence} presence Presence to compare with
+ * @returns {boolean}
+ */
+ equals(presence) {
+ return this === presence || (
+ presence &&
+ this.status === presence.status &&
+ this.game ? this.game.equals(presence.game) : !presence.game
+ );
+ }
+}
+
+/**
+ * Represents a game that is part of a user's presence.
+ */
+class Game {
+ constructor(data) {
+ /**
+ * The name of the game being played
+ * @type {string}
+ */
+ this.name = data.name;
+
+ /**
+ * The type of the game status
+ * @type {number}
+ */
+ this.type = data.type;
+
+ /**
+ * If the game is being streamed, a link to the stream
+ * @type {?string}
+ */
+ this.url = data.url || null;
+ }
+
+ /**
+ * Whether or not the game is being streamed
+ * @type {boolean}
+ * @readonly
+ */
+ get streaming() {
+ return this.type === 1;
+ }
+
+ /**
+ * Whether this game is equal to another game
+ * @param {Game} game Game to compare with
+ * @returns {boolean}
+ */
+ equals(game) {
+ return this === game || (
+ game &&
+ this.name === game.name &&
+ this.type === game.type &&
+ this.url === game.url
+ );
+ }
+}
+
+exports.Presence = Presence;
+exports.Game = Game;
diff --git a/node_modules/discord.js/src/structures/ReactionEmoji.js b/node_modules/discord.js/src/structures/ReactionEmoji.js
new file mode 100644
index 0000000..b6d2cdb
--- /dev/null
+++ b/node_modules/discord.js/src/structures/ReactionEmoji.js
@@ -0,0 +1,49 @@
+/**
+ * Represents a limited emoji set used for both custom and unicode emojis. Custom emojis
+ * will use this class opposed to the Emoji class when the client doesn't know enough
+ * information about them.
+ */
+class ReactionEmoji {
+ constructor(reaction, name, id) {
+ /**
+ * The message reaction this emoji refers to
+ * @type {MessageReaction}
+ */
+ this.reaction = reaction;
+
+ /**
+ * The name of this reaction emoji.
+ * @type {string}
+ */
+ this.name = name;
+
+ /**
+ * The ID of this reaction emoji.
+ * @type {string}
+ */
+ this.id = id;
+ }
+
+ /**
+ * The identifier of this emoji, used for message reactions
+ * @readonly
+ * @type {string}
+ */
+ get identifier() {
+ if (this.id) return `${this.name}:${this.id}`;
+ return encodeURIComponent(this.name);
+ }
+
+ /**
+ * Creates the text required to form a graphical emoji on Discord.
+ * @example
+ * // send the emoji used in a reaction to the channel the reaction is part of
+ * reaction.message.channel.sendMessage(`The emoji used is ${reaction.emoji}`);
+ * @returns {string}
+ */
+ toString() {
+ return this.id ? `<:${this.name}:${this.id}>` : this.name;
+ }
+}
+
+module.exports = ReactionEmoji;
diff --git a/node_modules/discord.js/src/structures/RichEmbed.js b/node_modules/discord.js/src/structures/RichEmbed.js
new file mode 100644
index 0000000..fbd9383
--- /dev/null
+++ b/node_modules/discord.js/src/structures/RichEmbed.js
@@ -0,0 +1,204 @@
+/**
+ * A rich embed to be sent with a message
+ * @param {Object} [data] Data to set in the rich embed
+ */
+class RichEmbed {
+ constructor(data = {}) {
+ /**
+ * Title for this Embed
+ * @type {string}
+ */
+ this.title = data.title;
+
+ /**
+ * Description for this Embed
+ * @type {string}
+ */
+ this.description = data.description;
+
+ /**
+ * URL for this Embed
+ * @type {string}
+ */
+ this.url = data.url;
+
+ /**
+ * Color for this Embed
+ * @type {number}
+ */
+ this.color = data.color;
+
+ /**
+ * Author for this Embed
+ * @type {Object}
+ */
+ this.author = data.author;
+
+ /**
+ * Timestamp for this Embed
+ * @type {Date}
+ */
+ this.timestamp = data.timestamp;
+
+ /**
+ * Fields for this Embed
+ * @type {Object[]}
+ */
+ this.fields = data.fields || [];
+
+ /**
+ * Thumbnail for this Embed
+ * @type {Object}
+ */
+ this.thumbnail = data.thumbnail;
+
+ /**
+ * Image for this Embed
+ * @type {Object}
+ */
+ this.image = data.image;
+
+ /**
+ * Footer for this Embed
+ * @type {Object}
+ */
+ this.footer = data.footer;
+ }
+
+ /**
+ * Sets the title of this embed
+ * @param {StringResolvable} title The title
+ * @returns {RichEmbed} This embed
+ */
+ setTitle(title) {
+ title = resolveString(title);
+ if (title.length > 256) throw new RangeError('RichEmbed titles may not exceed 256 characters.');
+ this.title = title;
+ return this;
+ }
+
+ /**
+ * Sets the description of this embed
+ * @param {StringResolvable} description The description
+ * @returns {RichEmbed} This embed
+ */
+ setDescription(description) {
+ description = resolveString(description);
+ if (description.length > 2048) throw new RangeError('RichEmbed descriptions may not exceed 2048 characters.');
+ this.description = description;
+ return this;
+ }
+
+ /**
+ * Sets the URL of this embed
+ * @param {string} url The URL
+ * @returns {RichEmbed} This embed
+ */
+ setURL(url) {
+ this.url = url;
+ return this;
+ }
+
+ /**
+ * Sets the color of this embed
+ * @param {string|number|number[]} color The color to set
+ * @returns {RichEmbed} This embed
+ */
+ setColor(color) {
+ let radix = 10;
+ if (color instanceof Array) {
+ color = (color[0] << 16) + (color[1] << 8) + color[2];
+ } else if (typeof color === 'string' && color.startsWith('#')) {
+ radix = 16;
+ color = color.replace('#', '');
+ }
+ color = parseInt(color, radix);
+ if (color < 0 || color > 0xFFFFFF) {
+ throw new RangeError('RichEmbed color must be within the range 0 - 16777215 (0xFFFFFF).');
+ } else if (color && isNaN(color)) {
+ throw new TypeError('Unable to convert RichEmbed color to a number.');
+ }
+ this.color = color;
+ return this;
+ }
+
+ /**
+ * Sets the author of this embed
+ * @param {StringResolvable} name The name of the author
+ * @param {string} [icon] The icon URL of the author
+ * @param {string} [url] The URL of the author
+ * @returns {RichEmbed} This embed
+ */
+ setAuthor(name, icon, url) {
+ this.author = { name: resolveString(name), icon_url: icon, url };
+ return this;
+ }
+
+ /**
+ * Sets the timestamp of this embed
+ * @param {Date} [timestamp=current date] The timestamp
+ * @returns {RichEmbed} This embed
+ */
+ setTimestamp(timestamp = new Date()) {
+ this.timestamp = timestamp;
+ return this;
+ }
+
+ /**
+ * Adds a field to the embed (max 25)
+ * @param {StringResolvable} name The name of the field
+ * @param {StringResolvable} value The value of the field
+ * @param {boolean} [inline=false] Set the field to display inline
+ * @returns {RichEmbed} This embed
+ */
+ addField(name, value, inline = false) {
+ if (this.fields.length >= 25) throw new RangeError('RichEmbeds may not exceed 25 fields.');
+ name = resolveString(name);
+ if (name.length > 256) throw new RangeError('RichEmbed field names may not exceed 256 characters.');
+ value = resolveString(value);
+ if (value.length > 1024) throw new RangeError('RichEmbed field values may not exceed 1024 characters.');
+ this.fields.push({ name: String(name), value: value, inline });
+ return this;
+ }
+
+ /**
+ * Set the thumbnail of this embed
+ * @param {string} url The URL of the thumbnail
+ * @returns {RichEmbed} This embed
+ */
+ setThumbnail(url) {
+ this.thumbnail = { url };
+ return this;
+ }
+
+ /**
+ * Set the image of this embed
+ * @param {string} url The URL of the thumbnail
+ * @returns {RichEmbed} This embed
+ */
+ setImage(url) {
+ this.image = { url };
+ return this;
+ }
+
+ /**
+ * Sets the footer of this embed
+ * @param {StringResolvable} text The text of the footer
+ * @param {string} [icon] The icon URL of the footer
+ * @returns {RichEmbed} This embed
+ */
+ setFooter(text, icon) {
+ text = resolveString(text);
+ if (text.length > 2048) throw new RangeError('RichEmbed footer text may not exceed 2048 characters.');
+ this.footer = { text, icon_url: icon };
+ return this;
+ }
+}
+
+module.exports = RichEmbed;
+
+function resolveString(data) {
+ if (typeof data === 'string') return data;
+ if (data instanceof Array) return data.join('\n');
+ return String(data);
+}
diff --git a/node_modules/discord.js/src/structures/Role.js b/node_modules/discord.js/src/structures/Role.js
new file mode 100644
index 0000000..c15ff4b
--- /dev/null
+++ b/node_modules/discord.js/src/structures/Role.js
@@ -0,0 +1,341 @@
+const Constants = require('../util/Constants');
+
+/**
+ * Represents a role on Discord
+ */
+class Role {
+ constructor(guild, data) {
+ /**
+ * The client that instantiated the role
+ * @name Role#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: guild.client });
+
+ /**
+ * The guild that the role belongs to
+ * @type {Guild}
+ */
+ this.guild = guild;
+
+ if (data) this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The ID of the role (unique to the guild it is part of)
+ * @type {string}
+ */
+ this.id = data.id;
+
+ /**
+ * The name of the role
+ * @type {string}
+ */
+ this.name = data.name;
+
+ /**
+ * The base 10 color of the role
+ * @type {number}
+ */
+ this.color = data.color;
+
+ /**
+ * If true, users that are part of this role will appear in a separate category in the users list
+ * @type {boolean}
+ */
+ this.hoist = data.hoist;
+
+ /**
+ * The position of the role in the role manager
+ * @type {number}
+ */
+ this.position = data.position;
+
+ /**
+ * The evaluated permissions number
+ * @type {number}
+ */
+ this.permissions = data.permissions;
+
+ /**
+ * Whether or not the role is managed by an external service
+ * @type {boolean}
+ */
+ this.managed = data.managed;
+
+ /**
+ * Whether or not the role can be mentioned by anyone
+ * @type {boolean}
+ */
+ this.mentionable = data.mentionable;
+ }
+
+ /**
+ * The timestamp the role was created at
+ * @type {number}
+ * @readonly
+ */
+ get createdTimestamp() {
+ return (this.id / 4194304) + 1420070400000;
+ }
+
+ /**
+ * The time the role was created
+ * @type {Date}
+ * @readonly
+ */
+ get createdAt() {
+ return new Date(this.createdTimestamp);
+ }
+
+ /**
+ * The hexadecimal version of the role color, with a leading hashtag.
+ * @type {string}
+ * @readonly
+ */
+ get hexColor() {
+ let col = this.color.toString(16);
+ while (col.length < 6) col = `0${col}`;
+ return `#${col}`;
+ }
+
+ /**
+ * The cached guild members that have this role.
+ * @type {Collection<string, GuildMember>}
+ * @readonly
+ */
+ get members() {
+ return this.guild.members.filter(m => m.roles.has(this.id));
+ }
+
+ /**
+ * Whether the role is editable by the client user.
+ * @type {boolean}
+ * @readonly
+ */
+ get editable() {
+ if (this.managed) return false;
+ const clientMember = this.guild.member(this.client.user);
+ if (!clientMember.hasPermission(Constants.PermissionFlags.MANAGE_ROLES_OR_PERMISSIONS)) return false;
+ return clientMember.highestRole.comparePositionTo(this) > 0;
+ }
+
+ /**
+ * Get an object mapping permission names to whether or not the role enables that permission
+ * @returns {Object<string, boolean>}
+ * @example
+ * // print the serialized role
+ * console.log(role.serialize());
+ */
+ serialize() {
+ const serializedPermissions = {};
+ for (const permissionName in Constants.PermissionFlags) {
+ serializedPermissions[permissionName] = this.hasPermission(permissionName);
+ }
+ return serializedPermissions;
+ }
+
+ /**
+ * Checks if the role has a permission.
+ * @param {PermissionResolvable} permission The permission to check for
+ * @param {boolean} [explicit=false] Whether to require the role to explicitly have the exact permission
+ * @returns {boolean}
+ * @example
+ * // see if a role can ban a member
+ * if (role.hasPermission('BAN_MEMBERS')) {
+ * console.log('This role can ban members');
+ * } else {
+ * console.log('This role can\'t ban members');
+ * }
+ */
+ hasPermission(permission, explicit = false) {
+ permission = this.client.resolver.resolvePermission(permission);
+ if (!explicit && (this.permissions & Constants.PermissionFlags.ADMINISTRATOR) > 0) return true;
+ return (this.permissions & permission) > 0;
+ }
+
+ /**
+ * Checks if the role has all specified permissions.
+ * @param {PermissionResolvable[]} permissions The permissions to check for
+ * @param {boolean} [explicit=false] Whether to require the role to explicitly have the exact permissions
+ * @returns {boolean}
+ */
+ hasPermissions(permissions, explicit = false) {
+ return permissions.every(p => this.hasPermission(p, explicit));
+ }
+
+ /**
+ * Compares this role's position to another role's.
+ * @param {Role} role Role to compare to this one
+ * @returns {number} Negative number if the this role's position is lower (other role's is higher),
+ * positive number if the this one is higher (other's is lower), 0 if equal
+ */
+ comparePositionTo(role) {
+ return this.constructor.comparePositions(this, role);
+ }
+
+ /**
+ * The data for a role
+ * @typedef {Object} RoleData
+ * @property {string} [name] The name of the role
+ * @property {number|string} [color] The color of the role, either a hex string or a base 10 number
+ * @property {boolean} [hoist] Whether or not the role should be hoisted
+ * @property {number} [position] The position of the role
+ * @property {string[]} [permissions] The permissions of the role
+ * @property {boolean} [mentionable] Whether or not the role should be mentionable
+ */
+
+ /**
+ * Edits the role
+ * @param {RoleData} data The new data for the role
+ * @returns {Promise<Role>}
+ * @example
+ * // edit a role
+ * role.edit({name: 'new role'})
+ * .then(r => console.log(`Edited role ${r}`))
+ * .catch(console.error);
+ */
+ edit(data) {
+ return this.client.rest.methods.updateGuildRole(this, data);
+ }
+
+ /**
+ * Set a new name for the role
+ * @param {string} name The new name of the role
+ * @returns {Promise<Role>}
+ * @example
+ * // set the name of the role
+ * role.setName('new role')
+ * .then(r => console.log(`Edited name of role ${r}`))
+ * .catch(console.error);
+ */
+ setName(name) {
+ return this.edit({ name });
+ }
+
+ /**
+ * Set a new color for the role
+ * @param {number|string} color The new color for the role, either a hex string or a base 10 number
+ * @returns {Promise<Role>}
+ * @example
+ * // set the color of a role
+ * role.setColor('#FF0000')
+ * .then(r => console.log(`Set color of role ${r}`))
+ * .catch(console.error);
+ */
+ setColor(color) {
+ return this.edit({ color });
+ }
+
+ /**
+ * Set whether or not the role should be hoisted
+ * @param {boolean} hoist Whether or not to hoist the role
+ * @returns {Promise<Role>}
+ * @example
+ * // set the hoist of the role
+ * role.setHoist(true)
+ * .then(r => console.log(`Role hoisted: ${r.hoist}`))
+ * .catch(console.error);
+ */
+ setHoist(hoist) {
+ return this.edit({ hoist });
+ }
+
+ /**
+ * Set the position of the role
+ * @param {number} position The position of the role
+ * @returns {Promise<Role>}
+ * @example
+ * // set the position of the role
+ * role.setPosition(1)
+ * .then(r => console.log(`Role position: ${r.position}`))
+ * .catch(console.error);
+ */
+ setPosition(position) {
+ return this.guild.setRolePosition(this, position).then(() => this);
+ }
+
+ /**
+ * Set the permissions of the role
+ * @param {string[]} permissions The permissions of the role
+ * @returns {Promise<Role>}
+ * @example
+ * // set the permissions of the role
+ * role.setPermissions(['KICK_MEMBERS', 'BAN_MEMBERS'])
+ * .then(r => console.log(`Role updated ${r}`))
+ * .catch(console.error);
+ */
+ setPermissions(permissions) {
+ return this.edit({ permissions });
+ }
+
+ /**
+ * Set whether this role is mentionable
+ * @param {boolean} mentionable Whether this role should be mentionable
+ * @returns {Promise<Role>}
+ * @example
+ * // make the role mentionable
+ * role.setMentionable(true)
+ * .then(r => console.log(`Role updated ${r}`))
+ * .catch(console.error);
+ */
+ setMentionable(mentionable) {
+ return this.edit({ mentionable });
+ }
+
+ /**
+ * Deletes the role
+ * @returns {Promise<Role>}
+ * @example
+ * // delete a role
+ * role.delete()
+ * .then(r => console.log(`Deleted role ${r}`))
+ * .catch(console.error);
+ */
+ delete() {
+ return this.client.rest.methods.deleteGuildRole(this);
+ }
+
+ /**
+ * Whether this role equals another role. It compares all properties, so for most operations
+ * it is advisable to just compare `role.id === role2.id` as it is much faster and is often
+ * what most users need.
+ * @param {Role} role Role to compare with
+ * @returns {boolean}
+ */
+ equals(role) {
+ return role &&
+ this.id === role.id &&
+ this.name === role.name &&
+ this.color === role.color &&
+ this.hoist === role.hoist &&
+ this.position === role.position &&
+ this.permissions === role.permissions &&
+ this.managed === role.managed;
+ }
+
+ /**
+ * When concatenated with a string, this automatically concatenates the role mention rather than the Role object.
+ * @returns {string}
+ */
+ toString() {
+ if (this.id === this.guild.id) return '@everyone';
+ return `<@&${this.id}>`;
+ }
+
+ /**
+ * Compares the positions of two roles.
+ * @param {Role} role1 First role to compare
+ * @param {Role} role2 Second role to compare
+ * @returns {number} Negative number if the first role's position is lower (second role's is higher),
+ * positive number if the first's is higher (second's is lower), 0 if equal
+ */
+ static comparePositions(role1, role2) {
+ if (role1.position === role2.position) return role2.id - role1.id;
+ return role1.position - role2.position;
+ }
+}
+
+module.exports = Role;
diff --git a/node_modules/discord.js/src/structures/TextChannel.js b/node_modules/discord.js/src/structures/TextChannel.js
new file mode 100644
index 0000000..9697abd
--- /dev/null
+++ b/node_modules/discord.js/src/structures/TextChannel.js
@@ -0,0 +1,96 @@
+const GuildChannel = require('./GuildChannel');
+const TextBasedChannel = require('./interface/TextBasedChannel');
+const Collection = require('../util/Collection');
+
+/**
+ * Represents a guild text channel on Discord.
+ * @extends {GuildChannel}
+ * @implements {TextBasedChannel}
+ */
+class TextChannel extends GuildChannel {
+ constructor(guild, data) {
+ super(guild, data);
+ this.type = 'text';
+ this.messages = new Collection();
+ this._typing = new Map();
+ }
+
+ setup(data) {
+ super.setup(data);
+
+ /**
+ * The topic of the text channel, if there is one.
+ * @type {?string}
+ */
+ this.topic = data.topic;
+
+ this.lastMessageID = data.last_message_id;
+ }
+
+ /**
+ * A collection of members that can see this channel, mapped by their ID.
+ * @type {Collection<string, GuildMember>}
+ * @readonly
+ */
+ get members() {
+ const members = new Collection();
+ for (const member of this.guild.members.values()) {
+ if (this.permissionsFor(member).hasPermission('READ_MESSAGES')) {
+ members.set(member.id, member);
+ }
+ }
+ return members;
+ }
+
+ /**
+ * Fetch all webhooks for the channel.
+ * @returns {Promise<Collection<string, Webhook>>}
+ */
+ fetchWebhooks() {
+ return this.client.rest.methods.getChannelWebhooks(this);
+ }
+
+ /**
+ * Create a webhook for the channel.
+ * @param {string} name The name of the webhook.
+ * @param {BufferResolvable} avatar The avatar for the webhook.
+ * @returns {Promise<Webhook>} webhook The created webhook.
+ * @example
+ * channel.createWebhook('Snek', 'http://snek.s3.amazonaws.com/topSnek.png')
+ * .then(webhook => console.log(`Created Webhook ${webhook}`))
+ * .catch(console.error)
+ */
+ createWebhook(name, avatar) {
+ return new Promise(resolve => {
+ if (avatar.startsWith('data:')) {
+ resolve(this.client.rest.methods.createWebhook(this, name, avatar));
+ } else {
+ this.client.resolver.resolveBuffer(avatar).then(data =>
+ resolve(this.client.rest.methods.createWebhook(this, name, data))
+ );
+ }
+ });
+ }
+
+ // These are here only for documentation purposes - they are implemented by TextBasedChannel
+ send() { return; }
+ sendMessage() { return; }
+ sendEmbed() { return; }
+ sendFile() { return; }
+ sendCode() { return; }
+ fetchMessage() { return; }
+ fetchMessages() { return; }
+ fetchPinnedMessages() { return; }
+ startTyping() { return; }
+ stopTyping() { return; }
+ get typing() { return; }
+ get typingCount() { return; }
+ createCollector() { return; }
+ awaitMessages() { return; }
+ bulkDelete() { return; }
+ _cacheMessage() { return; }
+}
+
+TextBasedChannel.applyToClass(TextChannel, true);
+
+module.exports = TextChannel;
diff --git a/node_modules/discord.js/src/structures/User.js b/node_modules/discord.js/src/structures/User.js
new file mode 100644
index 0000000..f714828
--- /dev/null
+++ b/node_modules/discord.js/src/structures/User.js
@@ -0,0 +1,277 @@
+const TextBasedChannel = require('./interface/TextBasedChannel');
+const Constants = require('../util/Constants');
+const Presence = require('./Presence').Presence;
+
+/**
+ * Represents a user on Discord.
+ * @implements {TextBasedChannel}
+ */
+class User {
+ constructor(client, data) {
+ /**
+ * The Client that created the instance of the the User.
+ * @name User#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: client });
+
+ if (data) this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The ID of the user
+ * @type {string}
+ */
+ this.id = data.id;
+
+ /**
+ * The username of the user
+ * @type {string}
+ */
+ this.username = data.username;
+
+ /**
+ * A discriminator based on username for the user
+ * @type {string}
+ */
+ this.discriminator = data.discriminator;
+
+ /**
+ * The ID of the user's avatar
+ * @type {string}
+ */
+ this.avatar = data.avatar;
+
+ /**
+ * Whether or not the user is a bot.
+ * @type {boolean}
+ */
+ this.bot = Boolean(data.bot);
+
+ /**
+ * The ID of the last message sent by the user, if one was sent.
+ * @type {?string}
+ */
+ this.lastMessageID = null;
+ }
+
+ patch(data) {
+ for (const prop of ['id', 'username', 'discriminator', 'avatar', 'bot']) {
+ if (typeof data[prop] !== 'undefined') this[prop] = data[prop];
+ }
+ if (data.token) this.client.token = data.token;
+ }
+
+ /**
+ * The timestamp the user was created at
+ * @type {number}
+ * @readonly
+ */
+ get createdTimestamp() {
+ return (this.id / 4194304) + 1420070400000;
+ }
+
+ /**
+ * The time the user was created
+ * @type {Date}
+ * @readonly
+ */
+ get createdAt() {
+ return new Date(this.createdTimestamp);
+ }
+
+ /**
+ * The presence of this user
+ * @type {Presence}
+ * @readonly
+ */
+ get presence() {
+ if (this.client.presences.has(this.id)) return this.client.presences.get(this.id);
+ for (const guild of this.client.guilds.values()) {
+ if (guild.presences.has(this.id)) return guild.presences.get(this.id);
+ }
+ return new Presence();
+ }
+
+ /**
+ * A link to the user's avatar (if they have one, otherwise null)
+ * @type {?string}
+ * @readonly
+ */
+ get avatarURL() {
+ if (!this.avatar) return null;
+ return Constants.Endpoints.avatar(this.id, this.avatar);
+ }
+
+ /**
+ * A link to the user's default avatar
+ * @type {string}
+ * @readonly
+ */
+ get defaultAvatarURL() {
+ let defaultAvatars = Object.values(Constants.DefaultAvatars);
+ let defaultAvatar = this.discriminator % defaultAvatars.length;
+ return Constants.Endpoints.assets(`${defaultAvatars[defaultAvatar]}.png`);
+ }
+
+ /**
+ * A link to the user's avatar if they have one. Otherwise a link to their default avatar will be returned
+ * @type {string}
+ * @readonly
+ */
+ get displayAvatarURL() {
+ return this.avatarURL || this.defaultAvatarURL;
+ }
+
+ /**
+ * The note that is set for the user
+ * <warn>This is only available when using a user account.</warn>
+ * @type {?string}
+ * @readonly
+ */
+ get note() {
+ return this.client.user.notes.get(this.id) || null;
+ }
+
+ /**
+ * Check whether the user is typing in a channel.
+ * @param {ChannelResolvable} channel The channel to check in
+ * @returns {boolean}
+ */
+ typingIn(channel) {
+ channel = this.client.resolver.resolveChannel(channel);
+ return channel._typing.has(this.id);
+ }
+
+ /**
+ * Get the time that the user started typing.
+ * @param {ChannelResolvable} channel The channel to get the time in
+ * @returns {?Date}
+ */
+ typingSinceIn(channel) {
+ channel = this.client.resolver.resolveChannel(channel);
+ return channel._typing.has(this.id) ? new Date(channel._typing.get(this.id).since) : null;
+ }
+
+ /**
+ * Get the amount of time the user has been typing in a channel for (in milliseconds), or -1 if they're not typing.
+ * @param {ChannelResolvable} channel The channel to get the time in
+ * @returns {number}
+ */
+ typingDurationIn(channel) {
+ channel = this.client.resolver.resolveChannel(channel);
+ return channel._typing.has(this.id) ? channel._typing.get(this.id).elapsedTime : -1;
+ }
+
+ /**
+ * The DM between the client's user and this user
+ * @type {?DMChannel}
+ */
+ get dmChannel() {
+ return this.client.channels.filter(c => c.type === 'dm').find(c => c.recipient.id === this.id);
+ }
+
+ /**
+ * Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful.
+ * @returns {Promise<DMChannel>}
+ */
+ deleteDM() {
+ return this.client.rest.methods.deleteChannel(this);
+ }
+
+ /**
+ * Sends a friend request to the user
+ * <warn>This is only available when using a user account.</warn>
+ * @returns {Promise<User>}
+ */
+ addFriend() {
+ return this.client.rest.methods.addFriend(this);
+ }
+
+ /**
+ * Removes the user from your friends
+ * <warn>This is only available when using a user account.</warn>
+ * @returns {Promise<User>}
+ */
+ removeFriend() {
+ return this.client.rest.methods.removeFriend(this);
+ }
+
+ /**
+ * Blocks the user
+ * <warn>This is only available when using a user account.</warn>
+ * @returns {Promise<User>}
+ */
+ block() {
+ return this.client.rest.methods.blockUser(this);
+ }
+
+ /**
+ * Unblocks the user
+ * <warn>This is only available when using a user account.</warn>
+ * @returns {Promise<User>}
+ */
+ unblock() {
+ return this.client.rest.methods.unblockUser(this);
+ }
+
+ /**
+ * Get the profile of the user
+ * <warn>This is only available when using a user account.</warn>
+ * @returns {Promise<UserProfile>}
+ */
+ fetchProfile() {
+ return this.client.rest.methods.fetchUserProfile(this);
+ }
+
+ /**
+ * Sets a note for the user
+ * <warn>This is only available when using a user account.</warn>
+ * @param {string} note The note to set for the user
+ * @returns {Promise<User>}
+ */
+ setNote(note) {
+ return this.client.rest.methods.setNote(this, note);
+ }
+
+ /**
+ * Checks if the user is equal to another. It compares ID, username, discriminator, avatar, and bot flags.
+ * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties.
+ * @param {User} user User to compare with
+ * @returns {boolean}
+ */
+ equals(user) {
+ let equal = user &&
+ this.id === user.id &&
+ this.username === user.username &&
+ this.discriminator === user.discriminator &&
+ this.avatar === user.avatar &&
+ this.bot === Boolean(user.bot);
+
+ return equal;
+ }
+
+ /**
+ * When concatenated with a string, this automatically concatenates the user's mention instead of the User object.
+ * @returns {string}
+ * @example
+ * // logs: Hello from <@123456789>!
+ * console.log(`Hello from ${user}!`);
+ */
+ toString() {
+ return `<@${this.id}>`;
+ }
+
+ // These are here only for documentation purposes - they are implemented by TextBasedChannel
+ send() { return; }
+ sendMessage() { return; }
+ sendEmbed() { return; }
+ sendFile() { return; }
+ sendCode() { return; }
+}
+
+TextBasedChannel.applyToClass(User);
+
+module.exports = User;
diff --git a/node_modules/discord.js/src/structures/UserConnection.js b/node_modules/discord.js/src/structures/UserConnection.js
new file mode 100644
index 0000000..6ee9fc5
--- /dev/null
+++ b/node_modules/discord.js/src/structures/UserConnection.js
@@ -0,0 +1,48 @@
+/**
+ * Represents a user connection (or "platform identity")
+ */
+class UserConnection {
+ constructor(user, data) {
+ /**
+ * The user that owns the Connection
+ * @type {User}
+ */
+ this.user = user;
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * The type of the Connection
+ * @type {string}
+ */
+ this.type = data.type;
+
+ /**
+ * The username of the connection account
+ * @type {string}
+ */
+ this.name = data.name;
+
+ /**
+ * The id of the connection account
+ * @type {string}
+ */
+ this.id = data.id;
+
+ /**
+ * Whether the connection is revoked
+ * @type {boolean}
+ */
+ this.revoked = data.revoked;
+
+ /**
+ * an array of partial server integrations (not yet implemented in this lib)
+ * @type {Object[]}
+ */
+ this.integrations = data.integrations;
+ }
+}
+
+module.exports = UserConnection;
diff --git a/node_modules/discord.js/src/structures/UserProfile.js b/node_modules/discord.js/src/structures/UserProfile.js
new file mode 100644
index 0000000..77f097c
--- /dev/null
+++ b/node_modules/discord.js/src/structures/UserProfile.js
@@ -0,0 +1,56 @@
+const Collection = require('../util/Collection');
+const UserConnection = require('./UserConnection');
+
+/**
+ * Represents a user's profile on Discord.
+ */
+class UserProfile {
+ constructor(user, data) {
+ /**
+ * The owner of the profile
+ * @type {User}
+ */
+ this.user = user;
+
+ /**
+ * The Client that created the instance of the the UserProfile.
+ * @name UserProfile#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: user.client });
+
+ /**
+ * Guilds that the client user and the user share
+ * @type {Collection<Guild>}
+ */
+ this.mutualGuilds = new Collection();
+
+ /**
+ * The user's connections
+ * @type {Collection<UserConnection>}
+ */
+ this.connections = new Collection();
+
+ this.setup(data);
+ }
+
+ setup(data) {
+ /**
+ * If the user has Discord Premium
+ * @type {boolean}
+ */
+ this.premium = data.premium;
+
+ for (const guild of data.mutual_guilds) {
+ if (this.client.guilds.has(guild.id)) {
+ this.mutualGuilds.set(guild.id, this.client.guilds.get(guild.id));
+ }
+ }
+ for (const connection of data.connected_accounts) {
+ this.connections.set(connection.id, new UserConnection(this.user, connection));
+ }
+ }
+}
+
+module.exports = UserProfile;
diff --git a/node_modules/discord.js/src/structures/VoiceChannel.js b/node_modules/discord.js/src/structures/VoiceChannel.js
new file mode 100644
index 0000000..848a6d5
--- /dev/null
+++ b/node_modules/discord.js/src/structures/VoiceChannel.js
@@ -0,0 +1,120 @@
+const GuildChannel = require('./GuildChannel');
+const Collection = require('../util/Collection');
+
+/**
+ * Represents a guild voice channel on Discord.
+ * @extends {GuildChannel}
+ */
+class VoiceChannel extends GuildChannel {
+ constructor(guild, data) {
+ super(guild, data);
+
+ /**
+ * The members in this voice channel.
+ * @type {Collection<string, GuildMember>}
+ */
+ this.members = new Collection();
+
+ this.type = 'voice';
+ }
+
+ setup(data) {
+ super.setup(data);
+
+ /**
+ * The bitrate of this voice channel
+ * @type {number}
+ */
+ this.bitrate = data.bitrate;
+
+ /**
+ * The maximum amount of users allowed in this channel - 0 means unlimited.
+ * @type {number}
+ */
+ this.userLimit = data.user_limit;
+ }
+
+ /**
+ * The voice connection for this voice channel, if the client is connected
+ * @type {?VoiceConnection}
+ * @readonly
+ */
+ get connection() {
+ const connection = this.guild.voiceConnection;
+ if (connection && connection.channel.id === this.id) return connection;
+ return null;
+ }
+
+ /**
+ * Checks if the client has permission join the voice channel
+ * @type {boolean}
+ */
+ get joinable() {
+ if (this.client.browser) return false;
+ return this.permissionsFor(this.client.user).hasPermission('CONNECT');
+ }
+
+ /**
+ * Checks if the client has permission to send audio to the voice channel
+ * @type {boolean}
+ */
+ get speakable() {
+ return this.permissionsFor(this.client.user).hasPermission('SPEAK');
+ }
+
+ /**
+ * Sets the bitrate of the channel
+ * @param {number} bitrate The new bitrate
+ * @returns {Promise<VoiceChannel>}
+ * @example
+ * // set the bitrate of a voice channel
+ * voiceChannel.setBitrate(48000)
+ * .then(vc => console.log(`Set bitrate to ${vc.bitrate} for ${vc.name}`))
+ * .catch(console.error);
+ */
+ setBitrate(bitrate) {
+ return this.edit({ bitrate });
+ }
+
+ /**
+ * Sets the user limit of the channel
+ * @param {number} userLimit The new user limit
+ * @returns {Promise<VoiceChannel>}
+ * @example
+ * // set the user limit of a voice channel
+ * voiceChannel.setUserLimit(42)
+ * .then(vc => console.log(`Set user limit to ${vc.userLimit} for ${vc.name}`))
+ * .catch(console.error);
+ */
+ setUserLimit(userLimit) {
+ return this.edit({ userLimit });
+ }
+
+ /**
+ * Attempts to join this voice channel
+ * @returns {Promise<VoiceConnection>}
+ * @example
+ * // join a voice channel
+ * voiceChannel.join()
+ * .then(connection => console.log('Connected!'))
+ * .catch(console.error);
+ */
+ join() {
+ if (this.client.browser) return Promise.reject(new Error('Voice connections are not available in browsers.'));
+ return this.client.voice.joinChannel(this);
+ }
+
+ /**
+ * Leaves this voice channel
+ * @example
+ * // leave a voice channel
+ * voiceChannel.leave();
+ */
+ leave() {
+ if (this.client.browser) return;
+ const connection = this.client.voice.connections.get(this.guild.id);
+ if (connection && connection.channel.id === this.id) connection.disconnect();
+ }
+}
+
+module.exports = VoiceChannel;
diff --git a/node_modules/discord.js/src/structures/Webhook.js b/node_modules/discord.js/src/structures/Webhook.js
new file mode 100644
index 0000000..96984ff
--- /dev/null
+++ b/node_modules/discord.js/src/structures/Webhook.js
@@ -0,0 +1,200 @@
+const path = require('path');
+const escapeMarkdown = require('../util/EscapeMarkdown');
+
+/**
+ * Represents a webhook
+ */
+class Webhook {
+ constructor(client, dataOrID, token) {
+ if (client) {
+ /**
+ * The Client that instantiated the Webhook
+ * @name Webhook#client
+ * @type {Client}
+ * @readonly
+ */
+ Object.defineProperty(this, 'client', { value: client });
+ if (dataOrID) this.setup(dataOrID);
+ } else {
+ this.id = dataOrID;
+ this.token = token;
+ Object.defineProperty(this, 'client', { value: this });
+ }
+ }
+
+ setup(data) {
+ /**
+ * The name of the webhook
+ * @type {string}
+ */
+ this.name = data.name;
+
+ /**
+ * The token for the webhook
+ * @type {string}
+ */
+ this.token = data.token;
+
+ /**
+ * The avatar for the webhook
+ * @type {string}
+ */
+ this.avatar = data.avatar;
+
+ /**
+ * The ID of the webhook
+ * @type {string}
+ */
+ this.id = data.id;
+
+ /**
+ * The guild the webhook belongs to
+ * @type {string}
+ */
+ this.guildID = data.guild_id;
+
+ /**
+ * The channel the webhook belongs to
+ * @type {string}
+ */
+ this.channelID = data.channel_id;
+
+ /**
+ * The owner of the webhook
+ * @type {User}
+ */
+ if (data.user) this.owner = data.user;
+ }
+
+ /**
+ * Options that can be passed into sendMessage, sendTTSMessage, sendFile, sendCode
+ * @typedef {Object} WebhookMessageOptions
+ * @property {boolean} [tts=false] Whether or not the message should be spoken aloud
+ * @property {boolean} [disableEveryone=this.options.disableEveryone] Whether or not @everyone and @here
+ * should be replaced with plain-text
+ */
+
+ /**
+ * Send a message with this webhook
+ * @param {StringResolvable} content The content to send.
+ * @param {WebhookMessageOptions} [options={}] The options to provide.
+ * @returns {Promise<Message|Message[]>}
+ * @example
+ * // send a message
+ * webhook.sendMessage('hello!')
+ * .then(message => console.log(`Sent message: ${message.content}`))
+ * .catch(console.error);
+ */
+ sendMessage(content, options = {}) {
+ return this.client.rest.methods.sendWebhookMessage(this, content, options);
+ }
+
+ /**
+ * Send a raw slack message with this webhook
+ * @param {Object} body The raw body to send.
+ * @returns {Promise}
+ * @example
+ * // send a slack message
+ * webhook.sendSlackMessage({
+ * 'username': 'Wumpus',
+ * 'attachments': [{
+ * 'pretext': 'this looks pretty cool',
+ * 'color': '#F0F',
+ * 'footer_icon': 'http://snek.s3.amazonaws.com/topSnek.png',
+ * 'footer': 'Powered by sneks',
+ * 'ts': Date.now() / 1000
+ * }]
+ * }).catch(console.error);
+ */
+ sendSlackMessage(body) {
+ return this.client.rest.methods.sendSlackWebhookMessage(this, body);
+ }
+
+ /**
+ * Send a text-to-speech message with this webhook
+ * @param {StringResolvable} content The content to send
+ * @param {WebhookMessageOptions} [options={}] The options to provide
+ * @returns {Promise<Message|Message[]>}
+ * @example
+ * // send a TTS message
+ * webhook.sendTTSMessage('hello!')
+ * .then(message => console.log(`Sent tts message: ${message.content}`))
+ * .catch(console.error);
+ */
+ sendTTSMessage(content, options = {}) {
+ Object.assign(options, { tts: true });
+ return this.client.rest.methods.sendWebhookMessage(this, content, options);
+ }
+
+ /**
+ * Send a file with this webhook
+ * @param {BufferResolvable} attachment The file to send
+ * @param {string} [fileName="file.jpg"] The name and extension of the file
+ * @param {StringResolvable} [content] Text message to send with the attachment
+ * @param {WebhookMessageOptions} [options] The options to provide
+ * @returns {Promise<Message>}
+ */
+ sendFile(attachment, fileName, content, options = {}) {
+ if (!fileName) {
+ if (typeof attachment === 'string') {
+ fileName = path.basename(attachment);
+ } else if (attachment && attachment.path) {
+ fileName = path.basename(attachment.path);
+ } else {
+ fileName = 'file.jpg';
+ }
+ }
+ return this.client.resolver.resolveBuffer(attachment).then(file =>
+ this.client.rest.methods.sendWebhookMessage(this, content, options, {
+ file,
+ name: fileName,
+ })
+ );
+ }
+
+ /**
+ * Send a code block with this webhook
+ * @param {string} lang Language for the code block
+ * @param {StringResolvable} content Content of the code block
+ * @param {WebhookMessageOptions} options The options to provide
+ * @returns {Promise<Message|Message[]>}
+ */
+ sendCode(lang, content, options = {}) {
+ if (options.split) {
+ if (typeof options.split !== 'object') options.split = {};
+ if (!options.split.prepend) options.split.prepend = `\`\`\`${lang || ''}\n`;
+ if (!options.split.append) options.split.append = '\n```';
+ }
+ content = escapeMarkdown(this.client.resolver.resolveString(content), true);
+ return this.sendMessage(`\`\`\`${lang || ''}\n${content}\n\`\`\``, options);
+ }
+
+ /**
+ * Edit the webhook.
+ * @param {string} name The new name for the Webhook
+ * @param {BufferResolvable} avatar The new avatar for the Webhook.
+ * @returns {Promise<Webhook>}
+ */
+ edit(name = this.name, avatar) {
+ if (avatar) {
+ return this.client.resolver.resolveBuffer(avatar).then(file => {
+ const dataURI = this.client.resolver.resolveBase64(file);
+ return this.client.rest.methods.editWebhook(this, name, dataURI);
+ });
+ }
+ return this.client.rest.methods.editWebhook(this, name).then(data => {
+ this.setup(data);
+ return this;
+ });
+ }
+
+ /**
+ * Delete the webhook
+ * @returns {Promise}
+ */
+ delete() {
+ return this.client.rest.methods.deleteWebhook(this);
+ }
+}
+
+module.exports = Webhook;
diff --git a/node_modules/discord.js/src/structures/interface/TextBasedChannel.js b/node_modules/discord.js/src/structures/interface/TextBasedChannel.js
new file mode 100644
index 0000000..353c0a9
--- /dev/null
+++ b/node_modules/discord.js/src/structures/interface/TextBasedChannel.js
@@ -0,0 +1,377 @@
+const path = require('path');
+const Message = require('../Message');
+const MessageCollector = require('../MessageCollector');
+const Collection = require('../../util/Collection');
+
+
+/**
+ * Interface for classes that have text-channel-like features
+ * @interface
+ */
+class TextBasedChannel {
+ constructor() {
+ /**
+ * A collection containing the messages sent to this channel.
+ * @type {Collection<string, Message>}
+ */
+ this.messages = new Collection();
+
+ /**
+ * The ID of the last message in the channel, if one was sent.
+ * @type {?string}
+ */
+ this.lastMessageID = null;
+ }
+
+ /**
+ * Options that can be passed into send, sendMessage, sendFile, sendEmbed, sendCode, and Message#reply
+ * @typedef {Object} MessageOptions
+ * @property {boolean} [tts=false] Whether or not the message should be spoken aloud
+ * @property {string} [nonce=''] The nonce for the message
+ * @property {Object} [embed] An embed for the message
+ * (see [here](https://discordapp.com/developers/docs/resources/channel#embed-object) for more details)
+ * @property {boolean} [disableEveryone=this.client.options.disableEveryone] Whether or not @everyone and @here
+ * should be replaced with plain-text
+ * @property {FileOptions|string} [file] A file to send with the message
+ * @property {string|boolean} [code] Language for optional codeblock formatting to apply
+ * @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if
+ * it exceeds the character limit. If an object is provided, these are the options for splitting the message.
+ */
+
+ /**
+ * @typedef {Object} FileOptions
+ * @property {BufferResolvable} attachment
+ * @property {string} [name='file.jpg']
+ */
+
+ /**
+ * Options for splitting a message
+ * @typedef {Object} SplitOptions
+ * @property {number} [maxLength=1950] Maximum character length per message piece
+ * @property {string} [char='\n'] Character to split the message with
+ * @property {string} [prepend=''] Text to prepend to every piece except the first
+ * @property {string} [append=''] Text to append to every piece except the last
+ */
+
+ /**
+ * Send a message to this channel
+ * @param {StringResolvable} [content] Text for the message
+ * @param {MessageOptions} [options={}] Options for the message
+ * @returns {Promise<Message|Message[]>}
+ * @example
+ * // send a message
+ * channel.send('hello!')
+ * .then(message => console.log(`Sent message: ${message.content}`))
+ * .catch(console.error);
+ */
+ send(content, options) {
+ if (!options && typeof content === 'object' && !(content instanceof Array)) {
+ options = content;
+ content = '';
+ } else if (!options) {
+ options = {};
+ }
+ if (options.file) {
+ if (typeof options.file === 'string') options.file = { attachment: options.file };
+ if (!options.file.name) {
+ if (typeof options.file.attachment === 'string') {
+ options.file.name = path.basename(options.file.attachment);
+ } else if (options.file.attachment && options.file.attachment.path) {
+ options.file.name = path.basename(options.file.attachment.path);
+ } else {
+ options.file.name = 'file.jpg';
+ }
+ }
+ return this.client.resolver.resolveBuffer(options.file.attachment).then(file =>
+ this.client.rest.methods.sendMessage(this, content, options, {
+ file,
+ name: options.file.name,
+ })
+ );
+ }
+ return this.client.rest.methods.sendMessage(this, content, options);
+ }
+
+ /**
+ * Send a message to this channel
+ * @param {StringResolvable} content Text for the message
+ * @param {MessageOptions} [options={}] Options for the message
+ * @returns {Promise<Message|Message[]>}
+ * @example
+ * // send a message
+ * channel.sendMessage('hello!')
+ * .then(message => console.log(`Sent message: ${message.content}`))
+ * .catch(console.error);
+ */
+ sendMessage(content, options) {
+ return this.send(content, options);
+ }
+
+ /**
+ * Send an embed to this channel
+ * @param {RichEmbed|Object} embed Embed for the message
+ * @param {string} [content] Text for the message
+ * @param {MessageOptions} [options] Options for the message
+ * @returns {Promise<Message>}
+ */
+ sendEmbed(embed, content, options) {
+ if (!options && typeof content === 'object') {
+ options = content;
+ content = '';
+ } else if (!options) {
+ options = {};
+ }
+ return this.send(content, Object.assign(options, { embed }));
+ }
+
+ /**
+ * Send a file to this channel
+ * @param {BufferResolvable} attachment File to send
+ * @param {string} [name='file.jpg'] Name and extension of the file
+ * @param {StringResolvable} [content] Text for the message
+ * @param {MessageOptions} [options] Options for the message
+ * @returns {Promise<Message>}
+ */
+ sendFile(attachment, name, content, options = {}) {
+ return this.send(content, Object.assign(options, { file: { attachment, name } }));
+ }
+
+ /**
+ * Send a code block to this channel
+ * @param {string} lang Language for the code block
+ * @param {StringResolvable} content Content of the code block
+ * @param {MessageOptions} [options] Options for the message
+ * @returns {Promise<Message|Message[]>}
+ */
+ sendCode(lang, content, options = {}) {
+ return this.send(content, Object.assign(options, { code: lang }));
+ }
+
+ /**
+ * Gets a single message from this channel, regardless of it being cached or not.
+ * <warn>This is only available when using a bot account.</warn>
+ * @param {string} messageID ID of the message to get
+ * @returns {Promise<Message>}
+ * @example
+ * // get message
+ * channel.fetchMessage('99539446449315840')
+ * .then(message => console.log(message.content))
+ * .catch(console.error);
+ */
+ fetchMessage(messageID) {
+ return this.client.rest.methods.getChannelMessage(this, messageID).then(data => {
+ const msg = data instanceof Message ? data : new Message(this, data, this.client);
+ this._cacheMessage(msg);
+ return msg;
+ });
+ }
+
+ /**
+ * The parameters to pass in when requesting previous messages from a channel. `around`, `before` and
+ * `after` are mutually exclusive. All the parameters are optional.
+ * @typedef {Object} ChannelLogsQueryOptions
+ * @property {number} [limit=50] Number of messages to acquire
+ * @property {string} [before] ID of a message to get the messages that were posted before it
+ * @property {string} [after] ID of a message to get the messages that were posted after it
+ * @property {string} [around] ID of a message to get the messages that were posted around it
+ */
+
+ /**
+ * Gets the past messages sent in this channel. Resolves with a collection mapping message ID's to Message objects.
+ * @param {ChannelLogsQueryOptions} [options={}] Query parameters to pass in
+ * @returns {Promise<Collection<string, Message>>}
+ * @example
+ * // get messages
+ * channel.fetchMessages({limit: 10})
+ * .then(messages => console.log(`Received ${messages.size} messages`))
+ * .catch(console.error);
+ */
+ fetchMessages(options = {}) {
+ return this.client.rest.methods.getChannelMessages(this, options).then(data => {
+ const messages = new Collection();
+ for (const message of data) {
+ const msg = new Message(this, message, this.client);
+ messages.set(message.id, msg);
+ this._cacheMessage(msg);
+ }
+ return messages;
+ });
+ }
+
+ /**
+ * Fetches the pinned messages of this channel and returns a collection of them.
+ * @returns {Promise<Collection<string, Message>>}
+ */
+ fetchPinnedMessages() {
+ return this.client.rest.methods.getChannelPinnedMessages(this).then(data => {
+ const messages = new Collection();
+ for (const message of data) {
+ const msg = new Message(this, message, this.client);
+ messages.set(message.id, msg);
+ this._cacheMessage(msg);
+ }
+ return messages;
+ });
+ }
+
+ /**
+ * Starts a typing indicator in the channel.
+ * @param {number} [count] The number of times startTyping should be considered to have been called
+ * @example
+ * // start typing in a channel
+ * channel.startTyping();
+ */
+ startTyping(count) {
+ if (typeof count !== 'undefined' && count < 1) throw new RangeError('Count must be at least 1.');
+ if (!this.client.user._typing.has(this.id)) {
+ this.client.user._typing.set(this.id, {
+ count: count || 1,
+ interval: this.client.setInterval(() => {
+ this.client.rest.methods.sendTyping(this.id);
+ }, 4000),
+ });
+ this.client.rest.methods.sendTyping(this.id);
+ } else {
+ const entry = this.client.user._typing.get(this.id);
+ entry.count = count || entry.count + 1;
+ }
+ }
+
+ /**
+ * Stops the typing indicator in the channel.
+ * The indicator will only stop if this is called as many times as startTyping().
+ * <info>It can take a few seconds for the client user to stop typing.</info>
+ * @param {boolean} [force=false] Whether or not to reset the call count and force the indicator to stop
+ * @example
+ * // stop typing in a channel
+ * channel.stopTyping();
+ * @example
+ * // force typing to fully stop in a channel
+ * channel.stopTyping(true);
+ */
+ stopTyping(force = false) {
+ if (this.client.user._typing.has(this.id)) {
+ const entry = this.client.user._typing.get(this.id);
+ entry.count--;
+ if (entry.count <= 0 || force) {
+ this.client.clearInterval(entry.interval);
+ this.client.user._typing.delete(this.id);
+ }
+ }
+ }
+
+ /**
+ * Whether or not the typing indicator is being shown in the channel.
+ * @type {boolean}
+ * @readonly
+ */
+ get typing() {
+ return this.client.user._typing.has(this.id);
+ }
+
+ /**
+ * Number of times `startTyping` has been called.
+ * @type {number}
+ * @readonly
+ */
+ get typingCount() {
+ if (this.client.user._typing.has(this.id)) return this.client.user._typing.get(this.id).count;
+ return 0;
+ }
+
+ /**
+ * Creates a Message Collector
+ * @param {CollectorFilterFunction} filter The filter to create the collector with
+ * @param {CollectorOptions} [options={}] The options to pass to the collector
+ * @returns {MessageCollector}
+ * @example
+ * // create a message collector
+ * const collector = channel.createCollector(
+ * m => m.content.includes('discord'),
+ * { time: 15000 }
+ * );
+ * collector.on('message', m => console.log(`Collected ${m.content}`));
+ * collector.on('end', collected => console.log(`Collected ${collected.size} items`));
+ */
+ createCollector(filter, options = {}) {
+ return new MessageCollector(this, filter, options);
+ }
+
+ /**
+ * An object containing the same properties as CollectorOptions, but a few more:
+ * @typedef {CollectorOptions} AwaitMessagesOptions
+ * @property {string[]} [errors] Stop/end reasons that cause the promise to reject
+ */
+
+ /**
+ * Similar to createCollector but in promise form. Resolves with a collection of messages that pass the specified
+ * filter.
+ * @param {CollectorFilterFunction} filter The filter function to use
+ * @param {AwaitMessagesOptions} [options={}] Optional options to pass to the internal collector
+ * @returns {Promise<Collection<string, Message>>}
+ * @example
+ * // await !vote messages
+ * const filter = m => m.content.startsWith('!vote');
+ * // errors: ['time'] treats ending because of the time limit as an error
+ * channel.awaitMessages(filter, { max: 4, time: 60000, errors: ['time'] })
+ * .then(collected => console.log(collected.size))
+ * .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`));
+ */
+ awaitMessages(filter, options = {}) {
+ return new Promise((resolve, reject) => {
+ const collector = this.createCollector(filter, options);
+ collector.on('end', (collection, reason) => {
+ if (options.errors && options.errors.includes(reason)) {
+ reject(collection);
+ } else {
+ resolve(collection);
+ }
+ });
+ });
+ }
+
+ /**
+ * Bulk delete given messages.
+ * <warn>This is only available when using a bot account.</warn>
+ * @param {Collection<string, Message>|Message[]|number} messages Messages to delete, or number of messages to delete
+ * @returns {Promise<Collection<string, Message>>} Deleted messages
+ */
+ bulkDelete(messages) {
+ if (!isNaN(messages)) return this.fetchMessages({ limit: messages }).then(msgs => this.bulkDelete(msgs));
+ if (messages instanceof Array || messages instanceof Collection) {
+ const messageIDs = messages instanceof Collection ? messages.keyArray() : messages.map(m => m.id);
+ return this.client.rest.methods.bulkDeleteMessages(this, messageIDs);
+ }
+ throw new TypeError('The messages must be an Array, Collection, or number.');
+ }
+
+ _cacheMessage(message) {
+ const maxSize = this.client.options.messageCacheMaxSize;
+ if (maxSize === 0) return null;
+ if (this.messages.size >= maxSize && maxSize > 0) this.messages.delete(this.messages.firstKey());
+ this.messages.set(message.id, message);
+ return message;
+ }
+}
+
+exports.applyToClass = (structure, full = false) => {
+ const props = ['send', 'sendMessage', 'sendEmbed', 'sendFile', 'sendCode'];
+ if (full) {
+ props.push(
+ '_cacheMessage',
+ 'fetchMessages',
+ 'fetchMessage',
+ 'bulkDelete',
+ 'startTyping',
+ 'stopTyping',
+ 'typing',
+ 'typingCount',
+ 'fetchPinnedMessages',
+ 'createCollector',
+ 'awaitMessages'
+ );
+ }
+ for (const prop of props) {
+ Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop));
+ }
+};