Cleanup Part 2: Electric Boogaloo (Reloaded) (#594)

* Cleanup Part 2: Electric Boogaloo (Reloaded)

* Moar cleanup

* Tweak NOT_A_PERMISSION error
This commit is contained in:
Schuyler Cebulskie
2016-09-04 05:08:09 -04:00
committed by Amish Shah
parent 5a9c42061f
commit 0b908f5bce
95 changed files with 946 additions and 1526 deletions

View File

@@ -15,10 +15,8 @@ const Collection = require('../util/Collection');
* @extends {EventEmitter}
*/
class Client extends EventEmitter {
/**
* Creates an instance of Client.
* @param {ClientOptions} [options] options to pass to the client
* @param {ClientOptions} [options] Options for the client
*/
constructor(options) {
super();
@@ -129,17 +127,13 @@ class Client extends EventEmitter {
* client.login(email, password);
*/
login(emailOrToken, password) {
if (password) {
// login with email and password
return this.rest.methods.loginEmailPassword(emailOrToken, password);
}
// login with token
if (password) return this.rest.methods.loginEmailPassword(emailOrToken, password);
return this.rest.methods.loginToken(emailOrToken);
}
/**
* Destroys the client and logs out. Resolves with null if successful.
* @returns {Promise<null, Error>}
* Destroys the client and logs out.
* @returns {Promise}
*/
destroy() {
return new Promise((resolve, reject) => {
@@ -152,8 +146,7 @@ class Client extends EventEmitter {
this._timeouts = [];
this._intervals = [];
resolve();
})
.catch(reject);
}).catch(reject);
});
}
@@ -176,7 +169,7 @@ class Client extends EventEmitter {
/**
* This shouldn't really be necessary to most developers as it is automatically invoked every 30 seconds, however
* if you wish to force a sync of Guild data, you can use this. Only applicable to user accounts.
* @param {array<Guild>} [guilds=this.guilds.array()] An array of guilds to sync.
* @param {Guild[]} [guilds=this.guilds.array()] An array of guilds to sync
*/
syncGuilds(guilds = this.guilds.array()) {
if (!this.user.bot) {
@@ -215,7 +208,6 @@ class Client extends EventEmitter {
get uptime() {
return this.readyTime ? Date.now() - this.readyTime : null;
}
}
module.exports = Client;

View File

@@ -23,11 +23,10 @@ class ClientDataManager {
this.client.guilds.set(guild.id, guild);
if (this.pastReady && !already) {
/**
* Emitted whenever the client joins a Guild.
*
* @event Client#guildCreate
* @param {Guild} guild the created guild.
*/
* Emitted whenever the client joins a Guild.
* @event Client#guildCreate
* @param {Guild} guild The created guild
*/
this.client.emit(Constants.Events.GUILD_CREATE, guild);
}
@@ -35,16 +34,13 @@ class ClientDataManager {
}
newUser(data) {
if (this.client.users.has(data.id)) {
return this.client.users.get(data.id);
}
if (this.client.users.has(data.id)) return this.client.users.get(data.id);
const user = new User(this.client, data);
this.client.users.set(user.id, user);
return user;
}
newChannel(data, $guild) {
let guild = $guild;
newChannel(data, guild) {
const already = this.client.channels.has(data.id);
let channel;
if (data.type === Constants.ChannelTypes.DM) {
@@ -65,22 +61,18 @@ class ClientDataManager {
}
if (channel) {
if (this.pastReady && !already) {
this.client.emit(Constants.Events.CHANNEL_CREATE, channel);
}
if (this.pastReady && !already) this.client.emit(Constants.Events.CHANNEL_CREATE, channel);
this.client.channels.set(channel.id, channel);
return channel;
}
return null;
}
killGuild(guild) {
const already = this.client.guilds.has(guild.id);
this.client.guilds.delete(guild.id);
if (already && this.pastReady) {
this.client.emit(Constants.Events.GUILD_DELETE, guild);
}
if (already && this.pastReady) this.client.emit(Constants.Events.GUILD_DELETE, guild);
}
killUser(user) {
@@ -89,17 +81,13 @@ class ClientDataManager {
killChannel(channel) {
this.client.channels.delete(channel.id);
if (channel instanceof GuildChannel) {
channel.guild.channels.delete(channel.id);
}
if (channel instanceof GuildChannel) channel.guild.channels.delete(channel.id);
}
updateGuild(currentGuild, newData) {
const oldGuild = cloneObject(currentGuild);
currentGuild.setup(newData);
if (this.pastReady) {
this.client.emit(Constants.Events.GUILD_UPDATE, oldGuild, currentGuild);
}
if (this.pastReady) this.client.emit(Constants.Events.GUILD_UPDATE, oldGuild, currentGuild);
}
updateChannel(currentChannel, newData) {

View File

@@ -2,13 +2,12 @@ const path = require('path');
const fs = require('fs');
const request = require('superagent');
const getStructure = name => require(`../structures/${name}`);
const User = getStructure('User');
const Message = getStructure('Message');
const Guild = getStructure('Guild');
const Channel = getStructure('Channel');
const GuildMember = getStructure('GuildMember');
const Constants = require('../util/constants');
const User = require(`../structures/User`);
const Message = require(`../structures/Message`);
const Guild = require(`../structures/Guild`);
const Channel = require(`../structures/Channel`);
const GuildMember = require(`../structures/GuildMember`);
/**
* The DataResolver identifies different objects and tries to resolve a specific piece of information from them, e.g.
@@ -16,7 +15,9 @@ const GuildMember = getStructure('GuildMember');
* @private
*/
class ClientDataResolver {
/**
* @param {Client} client The client the resolver is for
*/
constructor(client) {
this.client = client;
}
@@ -33,7 +34,7 @@ class ClientDataResolver {
/**
* Resolves a UserResolvable to a User object
* @param {UserResolvable} user the UserResolvable to identify
* @param {UserResolvable} user The UserResolvable to identify
* @returns {?User}
*/
resolveUser(user) {
@@ -60,18 +61,12 @@ class ClientDataResolver {
/**
* Resolves a GuildResolvable to a Guild object
* @param {GuildResolvable} guild the GuildResolvable to identify
* @param {GuildResolvable} guild The GuildResolvable to identify
* @returns {?Guild}
*/
resolveGuild(guild) {
if (guild instanceof Guild) {
return guild;
}
if (typeof guild === 'string') {
return this.client.guilds.get(guild);
}
if (guild instanceof Guild) return guild;
if (typeof guild === 'string') return this.client.guilds.get(guild);
return null;
}
@@ -84,21 +79,16 @@ class ClientDataResolver {
/**
* Resolves a GuildMemberResolvable to a GuildMember object
* @param {GuildResolvable} guild the guild that the member is part of
* @param {UserResolvable} user the user that is part of the guild
* @param {GuildResolvable} guild The guild that the member is part of
* @param {UserResolvable} user The user that is part of the guild
* @returns {?GuildMember}
*/
resolveGuildMember(guild, user) {
if (user instanceof GuildMember) {
return user;
}
if (user instanceof GuildMember) return user;
guild = this.resolveGuild(guild);
user = this.resolveUser(user);
if (!guild || !user) {
return null;
}
if (!guild || !user) return null;
return guild.members.get(user.id);
}
@@ -112,14 +102,11 @@ class ClientDataResolver {
/**
* Resolves a Base64Resolvable to a Base 64 image
* @param {Base64Resolvable} data the base 64 resolvable you want to resolve
* @param {Base64Resolvable} data The base 64 resolvable you want to resolve
* @returns {?string}
*/
resolveBase64(data) {
if (data instanceof Buffer) {
return `data:image/jpg;base64,${data.toString('base64')}`;
}
if (data instanceof Buffer) return `data:image/jpg;base64,${data.toString('base64')}`;
return data;
}
@@ -132,43 +119,49 @@ class ClientDataResolver {
/**
* Resolves a ChannelResolvable to a Channel object
* @param {ChannelResolvable} channel the channel resolvable to resolve
* @param {ChannelResolvable} channel The channel resolvable to resolve
* @returns {?Channel}
*/
resolveChannel(channel) {
if (channel instanceof Channel) {
return channel;
}
if (typeof channel === 'string') {
return this.client.channels.get(channel.id);
}
if (channel instanceof Channel) return channel;
if (typeof channel === 'string') return this.client.channels.get(channel.id);
return null;
}
/**
* Data that can be resolved to give a permission number. This can be:
* * A string
* * A permission number
* @typedef {string|number} PermissionResolvable
*/
/**
* Resolves a PermissionResolvable to a permission number
* @param {PermissionResolvable} permission The permission resolvable to resolve
* @returns {number}
*/
resolvePermission(permission) {
if (typeof permission === 'string') permission = Constants.PermissionFlags[permission];
if (!permission) throw Constants.Errors.NOT_A_PERMISSION;
return permission;
}
/**
* Data that can be resolved to give a string. This can be:
* * A string
* * An Array (joined with a new line delimiter to give a string)
* * Any object
* @typedef {string|Array|Object} StringResolvable
* * Any value
* @typedef {string|Array|*} StringResolvable
*/
/**
* Resolves a StringResolvable to a string
* @param {StringResolvable} data the string resolvable to resolve
* @param {StringResolvable} data The string resolvable to resolve
* @returns {string}
*/
resolveString(data) {
if (typeof data === 'string') {
return data;
}
if (data instanceof Array) {
return data.join('\n');
}
if (typeof data === 'string') return data;
if (data instanceof Array) return data.join('\n');
return String(data);
}
@@ -182,8 +175,8 @@ class ClientDataResolver {
/**
* Resolves a FileResolvable to a Buffer
* @param {FileResolvable} resource the file resolvable to resolve
* @returns {string|Buffer}
* @param {FileResolvable} resource The file resolvable to resolve
* @returns {Promise<Buffer>}
*/
resolveFile(resource) {
if (typeof resource === 'string') {
@@ -194,19 +187,19 @@ class ClientDataResolver {
.end((err, res) => err ? reject(err) : resolve(res.body));
} else {
const file = path.resolve(resource);
const stat = fs.statSync(file);
if (!stat.isFile()) {
throw new Error(`The file could not be found: ${file}`);
}
fs.readFile(file, (err, data) => {
if (err) reject(err); else resolve(data);
fs.stat(file, (err, stats) => {
if (err) reject(err);
if (!stats.isFile()) throw new Error(`The file could not be found: ${file}`);
fs.readFile(file, (err2, data) => {
if (err2) reject(err2); else resolve(data);
});
});
}
});
}
return Promise.resolve(resource);
if (resource instanceof Buffer) return Promise.resolve(resource);
return Promise.reject(new TypeError('resource is not a string or Buffer'));
}
}

View File

@@ -5,7 +5,6 @@ const Constants = require('../util/Constants');
* @private
*/
class ClientManager {
constructor(client) {
/**
* The Client that instantiated this Manager
@@ -21,25 +20,22 @@ class ClientManager {
/**
* Connects the Client to the WebSocket
* @param {string} token the authorization token
* @param {function} resolve function to run when connection is successful
* @param {function} reject function to run when connection fails
* @param {string} token The authorization token
* @param {function} resolve Function to run when connection is successful
* @param {function} reject Function to run when connection fails
*/
connectToWebSocket(token, resolve, reject) {
this.client.token = token;
this.client.rest.methods.getGateway()
.then(gateway => {
this.client.ws.connect(gateway);
this.client.once(Constants.Events.READY, () => resolve(token));
})
.catch(reject);
this.client.rest.methods.getGateway().then(gateway => {
this.client.ws.connect(gateway);
this.client.once(Constants.Events.READY, () => resolve(token));
}).catch(reject);
this.client.setTimeout(() => reject(Constants.Errors.TOOK_TOO_LONG), 1000 * 300);
}
/**
* Sets up a keep-alive interval to keep the Client's connection valid
* @param {number} time the interval in milliseconds at which heartbeat packets should be sent
* @param {number} time The interval in milliseconds at which heartbeat packets should be sent
*/
setupKeepAlive(time) {
this.heartbeatInterval = this.client.setInterval(() => {

View File

@@ -11,7 +11,6 @@ that WebSocket events don't clash with REST methods.
*/
class GenericAction {
constructor(client) {
this.client = client;
}
@@ -19,7 +18,6 @@ class GenericAction {
handle(data) {
return data;
}
}
module.exports = GenericAction;

View File

@@ -1,5 +1,4 @@
class ActionsManager {
constructor(client) {
this.client = client;

View File

@@ -1,7 +1,6 @@
const Action = require('./Action');
class ChannelCreateAction extends Action {
handle(data) {
const client = this.client;
const channel = client.dataManager.newChannel(data);

View File

@@ -1,7 +1,6 @@
const Action = require('./Action');
class ChannelDeleteAction extends Action {
constructor(client) {
super(client);
this.deleted = new Map();

View File

@@ -3,7 +3,6 @@ const Constants = require('../../util/Constants');
const cloneObject = require('../../util/CloneObject');
class ChannelUpdateAction extends Action {
handle(data) {
const client = this.client;
@@ -26,11 +25,10 @@ class ChannelUpdateAction extends Action {
}
/**
* Emitted whenever a channel is updated - e.g. name change, topic change.
*
* @event Client#channelUpdate
* @param {Channel} oldChannel the channel before the update
* @param {Channel} newChannel the channel after the update
*/
* Emitted whenever a channel is updated - e.g. name change, topic change.
* @event Client#channelUpdate
* @param {Channel} oldChannel The channel before the update
* @param {Channel} newChannel The channel after the update
*/
module.exports = ChannelUpdateAction;

View File

@@ -2,7 +2,6 @@ const Action = require('./Action');
const Constants = require('../../util/Constants');
class GuildBanRemove extends Action {
handle(data) {
const client = this.client;
const guild = client.guilds.get(data.guild_id);

View File

@@ -2,7 +2,6 @@ const Action = require('./Action');
const Constants = require('../../util/Constants');
class GuildDeleteAction extends Action {
constructor(client) {
super(client);
this.deleted = new Map();
@@ -45,9 +44,8 @@ class GuildDeleteAction extends Action {
/**
* Emitted whenever a guild becomes unavailable, likely due to a server outage.
*
* @event Client#guildUnavailable
* @param {Guild} guild the guild that has become unavailable.
*/
* @param {Guild} guild The guild that has become unavailable.
*/
module.exports = GuildDeleteAction;

View File

@@ -2,7 +2,6 @@ const Action = require('./Action');
const Constants = require('../../util/Constants');
class GuildMemberRemoveAction extends Action {
constructor(client) {
super(client);
this.deleted = new Map();
@@ -43,10 +42,9 @@ class GuildMemberRemoveAction extends Action {
/**
* Emitted whenever a member leaves a guild, or is kicked.
*
* @event Client#guildMemberRemove
* @param {Guild} guild the guild that the member has left.
* @param {GuildMember} member the member that has left the guild.
* @param {Guild} guild The guild that the member has left.
* @param {GuildMember} member The member that has left the guild.
*/
module.exports = GuildMemberRemoveAction;

View File

@@ -3,7 +3,6 @@ const Constants = require('../../util/Constants');
const Role = require('../../structures/Role');
class GuildRoleCreate extends Action {
handle(data) {
const client = this.client;
@@ -25,11 +24,10 @@ class GuildRoleCreate extends Action {
}
/**
* Emitted whenever a guild role is created.
*
* @event Client#guildRoleCreate
* @param {Guild} guild the guild that the role was created in.
* @param {Role} role the role that was created.
*/
* Emitted whenever a guild role is created.
* @event Client#guildRoleCreate
* @param {Guild} guild The guild that the role was created in.
* @param {Role} role The role that was created.
*/
module.exports = GuildRoleCreate;

View File

@@ -2,7 +2,6 @@ const Action = require('./Action');
const Constants = require('../../util/Constants');
class GuildRoleDeleteAction extends Action {
constructor(client) {
super(client);
this.deleted = new Map();
@@ -39,11 +38,10 @@ class GuildRoleDeleteAction extends Action {
}
/**
* Emitted whenever a guild role is deleted.
*
* @event Client#guildRoleDelete
* @param {Guild} guild the guild that the role was deleted in.
* @param {Role} role the role that was deleted.
*/
* Emitted whenever a guild role is deleted.
* @event Client#guildRoleDelete
* @param {Guild} guild The guild that the role was deleted in.
* @param {Role} role The role that was deleted.
*/
module.exports = GuildRoleDeleteAction;

View File

@@ -3,7 +3,6 @@ const Constants = require('../../util/Constants');
const cloneObject = require('../../util/CloneObject');
class GuildRoleUpdateAction extends Action {
handle(data) {
const client = this.client;
@@ -33,12 +32,11 @@ class GuildRoleUpdateAction extends Action {
}
/**
* Emitted whenever a guild role is updated.
*
* @event Client#guildRoleUpdated
* @param {Guild} guild the guild that the role was updated in.
* @param {Role} oldRole the role before the update.
* @param {Role} newRole the role after the update.
*/
* Emitted whenever a guild role is updated.
* @event Client#guildRoleUpdated
* @param {Guild} guild The guild that the role was updated in.
* @param {Role} oldRole The role before the update.
* @param {Role} newRole The role after the update.
*/
module.exports = GuildRoleUpdateAction;

View File

@@ -1,7 +1,6 @@
const Action = require('./Action');
class GuildSync extends Action {
handle(data) {
const client = this.client;

View File

@@ -3,7 +3,6 @@ const Constants = require('../../util/Constants');
const cloneObject = require('../../util/CloneObject');
class GuildUpdateAction extends Action {
handle(data) {
const client = this.client;
@@ -26,11 +25,10 @@ class GuildUpdateAction extends Action {
}
/**
* Emitted whenever a guild is updated - e.g. name change.
*
* @event Client#guildUpdate
* @param {Guild} oldGuild the guild before the update.
* @param {Guild} newGuild the guild after the update.
*/
* Emitted whenever a guild is updated - e.g. name change.
* @event Client#guildUpdate
* @param {Guild} oldGuild The guild before the update.
* @param {Guild} newGuild The guild after the update.
*/
module.exports = GuildUpdateAction;

View File

@@ -2,7 +2,6 @@ const Action = require('./Action');
const Message = require('../../structures/Message');
class MessageCreateAction extends Action {
handle(data) {
const client = this.client;

View File

@@ -1,7 +1,6 @@
const Action = require('./Action');
class MessageDeleteAction extends Action {
constructor(client) {
super(client);
this.deleted = new Map();

View File

@@ -3,7 +3,6 @@ const Collection = require('../../util/Collection');
const Constants = require('../../util/Constants');
class MessageDeleteBulkAction extends Action {
handle(data) {
const client = this.client;
const channel = client.channels.get(data.channel_id);
@@ -12,9 +11,7 @@ class MessageDeleteBulkAction extends Action {
const messages = new Collection();
for (const id of ids) {
const message = channel.messages.get(id);
if (message) {
messages.set(message.id, message);
}
if (message) messages.set(message.id, message);
}
if (messages.size > 0) client.emit(Constants.Events.MESSAGE_BULK_DELETE, messages);

View File

@@ -3,7 +3,6 @@ const Constants = require('../../util/Constants');
const cloneObject = require('../../util/CloneObject');
class MessageUpdateAction extends Action {
handle(data) {
const client = this.client;
@@ -34,11 +33,10 @@ class MessageUpdateAction extends Action {
}
/**
* Emitted whenever a message is updated - e.g. embed or content change.
*
* @event Client#messageUpdate
* @param {Message} oldMessage the message before the update.
* @param {Message} newMessage the message after the update.
*/
* Emitted whenever a message is updated - e.g. embed or content change.
* @event Client#messageUpdate
* @param {Message} oldMessage The message before the update.
* @param {Message} newMessage The message after the update.
*/
module.exports = MessageUpdateAction;

View File

@@ -1,7 +1,6 @@
const Action = require('./Action');
class UserGetAction extends Action {
handle(data) {
const client = this.client;
const user = client.dataManager.newUser(data);

View File

@@ -3,7 +3,6 @@ const Constants = require('../../util/Constants');
const cloneObject = require('../../util/CloneObject');
class UserUpdateAction extends Action {
handle(data) {
const client = this.client;
@@ -32,11 +31,10 @@ class UserUpdateAction extends Action {
}
/**
* Emitted whenever a detail of the logged in User changes - e.g. username.
*
* @event Client#userUpdate
* @param {ClientUser} oldClientUser the client user before the update.
* @param {ClientUser} newClientUser the client user after the update.
*/
* Emitted whenever a detail of the logged in User changes - e.g. username.
* @event Client#userUpdate
* @param {ClientUser} oldClientUser The client user before the update.
* @param {ClientUser} newClientUser The client user after the update.
*/
module.exports = UserUpdateAction;

View File

@@ -26,17 +26,12 @@ class APIRequest {
gen() {
const apiRequest = request[this.method](this.url);
if (this.auth) {
apiRequest.set('authorization', this.getAuth());
}
if (this.auth) apiRequest.set('authorization', this.getAuth());
if (this.file && this.file.file) {
apiRequest.set('Content-Type', 'multipart/form-data');
apiRequest.attach('file', this.file.file, this.file.name);
}
if (this.data) {
apiRequest.send(this.data);
}
if (this.data) apiRequest.send(this.data);
apiRequest.set('User-Agent', this.rest.userAgentManager.userAgent);
return apiRequest;
}

View File

@@ -5,7 +5,6 @@ const APIRequest = require('./APIRequest');
const Constants = require('../../util/Constants');
class RESTManager {
constructor(client) {
this.client = client;
this.handlers = {};

View File

@@ -1,11 +1,11 @@
const Constants = require('../../util/Constants');
const Collection = require('../../util/Collection');
const getStructure = name => require(`../../structures/${name}`);
const User = getStructure('User');
const GuildMember = getStructure('GuildMember');
const Role = getStructure('Role');
const Invite = getStructure('Invite');
const requireStructure = name => require(`../../structures/${name}`);
const User = requireStructure('User');
const GuildMember = requireStructure('GuildMember');
const Role = requireStructure('Role');
const Invite = requireStructure('Invite');
class RESTMethods {
constructor(restManager) {
@@ -45,10 +45,9 @@ class RESTMethods {
});
}
sendMessage($channel, content, tts, nonce, file) {
sendMessage(channel, content, tts, nonce, file) {
return new Promise((resolve, reject) => {
const $this = this;
let channel = $channel;
function req() {
$this.rest.makeRequest('post', Constants.Endpoints.channelMessages(channel.id), true, {
@@ -101,11 +100,9 @@ class RESTMethods {
return new Promise((resolve, reject) => {
this.rest.makeRequest('patch', Constants.Endpoints.channelMessage(message.channel.id, message.id), true, {
content,
})
.then(data => {
}).then(data => {
resolve(this.rest.client.actions.MessageUpdate.handle(data).updated);
})
.catch(reject);
}).catch(reject);
});
}
@@ -114,11 +111,9 @@ class RESTMethods {
this.rest.makeRequest('post', Constants.Endpoints.guildChannels(guild.id), true, {
name: channelName,
type: channelType,
})
.then(data => {
resolve(this.rest.client.actions.ChannelCreate.handle(data).channel);
})
.catch(reject);
}).then(data => {
resolve(this.rest.client.actions.ChannelCreate.handle(data).channel);
}).catch(reject);
});
}
@@ -126,98 +121,73 @@ class RESTMethods {
const dmChannel = Array.from(this.rest.client.channels.values())
.filter(channel => channel.recipient)
.filter(channel => channel.recipient.id === recipient.id);
return dmChannel[0];
}
createDM(recipient) {
return new Promise((resolve, reject) => {
const dmChannel = this.getExistingDM(recipient);
if (dmChannel) {
return resolve(dmChannel);
}
if (dmChannel) return resolve(dmChannel);
return this.rest.makeRequest('post', Constants.Endpoints.userChannels(this.rest.client.user.id), true, {
recipient_id: recipient.id,
})
.then(data => resolve(this.rest.client.actions.ChannelCreate.handle(data).channel))
.catch(reject);
}).then(data => resolve(this.rest.client.actions.ChannelCreate.handle(data).channel)).catch(reject);
});
}
deleteChannel($channel) {
let channel = $channel;
deleteChannel(channel) {
return new Promise((resolve, reject) => {
if (channel instanceof User || channel instanceof GuildMember) {
channel = this.getExistingDM(channel);
}
this.rest.makeRequest('del', Constants.Endpoints.channel(channel.id), true)
.then($data => {
const data = $data;
data.id = channel.id;
resolve(this.rest.client.actions.ChannelDelete.handle(data).channel);
})
.catch(reject);
if (channel instanceof User || channel instanceof GuildMember) channel = this.getExistingDM(channel);
this.rest.makeRequest('del', Constants.Endpoints.channel(channel.id), true).then(data => {
data.id = channel.id;
resolve(this.rest.client.actions.ChannelDelete.handle(data).channel);
}).catch(reject);
});
}
updateChannel(channel, $data) {
const data = $data;
updateChannel(channel, data) {
return new Promise((resolve, reject) => {
data.name = (data.name || channel.name).trim();
data.topic = data.topic || channel.topic;
data.position = data.position || channel.position;
data.bitrate = data.bitrate || channel.bitrate;
this.rest.makeRequest('patch', Constants.Endpoints.channel(channel.id), true, data)
.then(newData => {
resolve(this.rest.client.actions.ChannelUpdate.handle(newData).updated);
})
.catch(reject);
this.rest.makeRequest('patch', Constants.Endpoints.channel(channel.id), true, data).then(newData => {
resolve(this.rest.client.actions.ChannelUpdate.handle(newData).updated);
}).catch(reject);
});
}
leaveGuild(guild) {
if (guild.ownerID === this.rest.client.user.id) {
return this.deleteGuild(guild);
}
if (guild.ownerID === this.rest.client.user.id) return this.deleteGuild(guild);
return new Promise((resolve, reject) => {
this.rest.makeRequest('del', Constants.Endpoints.meGuild(guild.id), true)
.then(() => {
resolve(this.rest.client.actions.GuildDelete.handle({ id: guild.id }).guild);
})
.catch(reject);
this.rest.makeRequest('del', Constants.Endpoints.meGuild(guild.id), true).then(() => {
resolve(this.rest.client.actions.GuildDelete.handle({ id: guild.id }).guild);
}).catch(reject);
});
}
// untested but probably will work
deleteGuild(guild) {
return new Promise((resolve, reject) => {
this.rest.makeRequest('del', Constants.Endpoints.guild(guild.id), true)
.then(() => {
resolve(this.rest.client.actions.GuildDelete.handle({ id: guild.id }).guild);
})
.catch(reject);
this.rest.makeRequest('del', Constants.Endpoints.guild(guild.id), true).then(() => {
resolve(this.rest.client.actions.GuildDelete.handle({ id: guild.id }).guild);
}).catch(reject);
});
}
getUser(userID) {
return new Promise((resolve, reject) => {
this.rest.makeRequest('get', Constants.Endpoints.user(userID), true)
.then((data) => {
resolve(this.rest.client.actions.UserGet.handle(data).user);
})
.catch(reject);
this.rest.makeRequest('get', Constants.Endpoints.user(userID), true).then((data) => {
resolve(this.rest.client.actions.UserGet.handle(data).user);
}).catch(reject);
});
}
updateCurrentUser(_data) {
return new Promise((resolve, reject) => {
const user = this.rest.client.user;
const data = {};
const data = {};
data.username = _data.username || user.username;
data.avatar = this.rest.client.resolver.resolveBase64(_data.avatar) || user.avatar;
if (!user.bot) {
@@ -234,44 +204,15 @@ class RESTMethods {
updateGuild(guild, _data) {
return new Promise((resolve, reject) => {
/*
can contain:
name, region, verificationLevel, afkChannel, afkTimeout, icon, owner, splash
*/
const data = {};
if (_data.name) {
data.name = _data.name;
}
if (_data.region) {
data.region = _data.region;
}
if (_data.verificationLevel) {
data.verification_level = Number(_data.verificationLevel);
}
if (_data.afkChannel) {
data.afk_channel_id = this.rest.client.resolver.resolveChannel(_data.afkChannel).id;
}
if (_data.afkTimeout) {
data.afk_timeout = Number(_data.afkTimeout);
}
if (_data.icon) {
data.icon = this.rest.client.resolver.resolveBase64(_data.icon);
}
if (_data.owner) {
data.owner_id = this.rest.client.resolver.resolveUser(_data.owner).id;
}
if (_data.splash) {
data.splash = this.rest.client.resolver.resolveBase64(_data.splash);
}
if (_data.name) data.name = _data.name;
if (_data.region) data.region = _data.region;
if (_data.verificationLevel) data.verification_level = Number(_data.verificationLevel);
if (_data.afkChannel) data.afk_channel_id = this.rest.client.resolver.resolveChannel(_data.afkChannel).id;
if (_data.afkTimeout) data.afk_timeout = Number(_data.afkTimeout);
if (_data.icon) data.icon = this.rest.client.resolver.resolveBase64(_data.icon);
if (_data.owner) data.owner_id = this.rest.client.resolver.resolveUser(_data.owner).id;
if (_data.splash) data.splash = this.rest.client.resolver.resolveBase64(_data.splash);
this.rest.makeRequest('patch', Constants.Endpoints.guild(guild.id), true, data)
.then(newData => resolve(this.rest.client.actions.GuildUpdate.handle(newData).updated))
@@ -281,40 +222,34 @@ class RESTMethods {
kickGuildMember(guild, member) {
return new Promise((resolve, reject) => {
this.rest.makeRequest('del', Constants.Endpoints.guildMember(guild.id, member.id), true)
.then(() => {
resolve(this.rest.client.actions.GuildMemberRemove.handle({
guild_id: guild.id,
user: member.user,
}).member);
})
.catch(reject);
this.rest.makeRequest('del', Constants.Endpoints.guildMember(guild.id, member.id), true).then(() => {
resolve(this.rest.client.actions.GuildMemberRemove.handle({
guild_id: guild.id,
user: member.user,
}).member);
}).catch(reject);
});
}
createGuildRole(guild) {
return new Promise((resolve, reject) => {
this.rest.makeRequest('post', Constants.Endpoints.guildRoles(guild.id), true)
.then(role => {
resolve(this.rest.client.actions.GuildRoleCreate.handle({
guild_id: guild.id,
role,
}).role);
})
.catch(reject);
this.rest.makeRequest('post', Constants.Endpoints.guildRoles(guild.id), true).then(role => {
resolve(this.rest.client.actions.GuildRoleCreate.handle({
guild_id: guild.id,
role,
}).role);
}).catch(reject);
});
}
deleteGuildRole(role) {
return new Promise((resolve, reject) => {
this.rest.makeRequest('del', Constants.Endpoints.guildRole(role.guild.id, role.id), true)
.then(() => {
resolve(this.rest.client.actions.GuildRoleDelete.handle({
guild_id: role.guild.id,
role_id: role.id,
}).role);
})
.catch(reject);
this.rest.makeRequest('del', Constants.Endpoints.guildRole(role.guild.id, role.id), true).then(() => {
resolve(this.rest.client.actions.GuildRoleDelete.handle({
guild_id: role.guild.id,
role_id: role.id,
}).role);
}).catch(reject);
});
}
@@ -338,23 +273,14 @@ class RESTMethods {
getChannelMessages(channel, payload = {}) {
return new Promise((resolve, reject) => {
const params = [];
if (payload.limit) {
params.push(`limit=${payload.limit}`);
}
if (payload.around) {
params.push(`around=${payload.around}`);
} else if (payload.before) {
params.push(`before=${payload.before}`);
} else if (payload.after) {
params.push(`after=${payload.after}`);
}
if (payload.limit) params.push(`limit=${payload.limit}`);
if (payload.around) params.push(`around=${payload.around}`);
else if (payload.before) params.push(`before=${payload.before}`);
else if (payload.after) params.push(`after=${payload.after}`);
let request = Constants.Endpoints.channelMessages(channel.id);
if (params.length > 0) {
request += `?${params.join('&')}`;
}
this.rest.makeRequest('get', request, true)
let endpoint = Constants.Endpoints.channelMessages(channel.id);
if (params.length > 0) endpoint += `?${params.join('&')}`;
this.rest.makeRequest('get', endpoint, true)
.then(resolve)
.catch(reject);
});
@@ -362,13 +288,9 @@ class RESTMethods {
updateGuildMember(member, data) {
return new Promise((resolve, reject) => {
if (data.channel) {
data.channel_id = this.rest.client.resolver.resolveChannel(data.channel).id;
}
if (data.channel) data.channel_id = this.rest.client.resolver.resolveChannel(data.channel).id;
if (data.roles) {
if (data.roles instanceof Map) {
data.roles = data.roles.array();
}
if (data.roles instanceof Collection) data.roles = data.roles.array();
data.roles = data.roles.map(role => role instanceof Role ? role.id : role);
}
@@ -379,6 +301,7 @@ class RESTMethods {
endpoint = Constants.Endpoints.stupidInconsistentGuildEndpoint(member.guild.id);
}
}
this.rest.makeRequest('patch', endpoint, true, data)
.then(resData => resolve(member.guild._updateMember(member, resData).mem))
.catch(reject);
@@ -407,9 +330,7 @@ class RESTMethods {
unbanGuildMember(guild, member) {
return new Promise((resolve, reject) => {
member = this.rest.client.resolver.resolveUser(member);
if (!member) {
throw new Error('cannot unban a user that is not a user resolvable');
}
if (!member) throw new Error('cannot unban a user that is not a user resolvable');
const listener = (eGuild, eUser) => {
if (guild.id === guild.id && member.id === eUser.id) {
this.rest.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener);
@@ -423,48 +344,30 @@ class RESTMethods {
getGuildBans(guild) {
return new Promise((resolve, reject) => {
this.rest.makeRequest('get', Constants.Endpoints.guildBans(guild.id), true)
.then(banItems => {
const bannedUsers = new Collection();
for (const banItem of banItems) {
const user = this.rest.client.dataManager.newUser(banItem.user);
bannedUsers.set(user.id, user);
}
resolve(bannedUsers);
})
.catch(reject);
this.rest.makeRequest('get', Constants.Endpoints.guildBans(guild.id), true).then(banItems => {
const bannedUsers = new Collection();
for (const banItem of banItems) {
const user = this.rest.client.dataManager.newUser(banItem.user);
bannedUsers.set(user.id, user);
}
resolve(bannedUsers);
}).catch(reject);
});
}
updateGuildRole(role, _data) {
return new Promise((resolve, reject) => {
/*
can contain:
name, position, permissions, color, hoist
*/
const data = {};
data.name = _data.name || role.name;
data.position = _data.position || role.position;
data.color = _data.color || role.color;
if (data.color.startsWith('#')) {
data.color = parseInt(data.color.replace('#', ''), 16);
}
if (typeof _data.hoist !== 'undefined') {
data.hoist = _data.hoist;
} else {
data.hoist = role.hoist;
}
if (data.color.startsWith('#')) data.color = parseInt(data.color.replace('#', ''), 16);
data.hoist = typeof _data.hoist !== 'undefined' ? _data.hoist : role.hoist;
if (_data.permissions) {
let perms = 0;
for (let perm of _data.permissions) {
if (typeof perm === 'string') {
perm = Constants.PermissionFlags[perm];
}
if (typeof perm === 'string') perm = Constants.PermissionFlags[perm];
perms |= perm;
}
data.permissions = perms;
@@ -472,14 +375,12 @@ class RESTMethods {
data.permissions = role.permissions;
}
this.rest.makeRequest('patch', Constants.Endpoints.guildRole(role.guild.id, role.id), true, data)
.then(_role => {
resolve(this.rest.client.actions.GuildRoleUpdate.handle({
role: _role,
guild_id: role.guild.id,
}).updated);
})
.catch(reject);
this.rest.makeRequest('patch', Constants.Endpoints.guildRole(role.guild.id, role.id), true, data).then(_role => {
resolve(this.rest.client.actions.GuildRoleUpdate.handle({
role: _role,
guild_id: role.guild.id,
}).updated);
}).catch(reject);
});
}
@@ -526,16 +427,14 @@ class RESTMethods {
getGuildInvites(guild) {
return new Promise((resolve, reject) => {
this.rest.makeRequest('get', Constants.Endpoints.guildInvites(guild.id), true)
.then(inviteItems => {
const invites = new Collection();
for (const inviteItem of inviteItems) {
const invite = new Invite(this.rest.client, inviteItem);
invites.set(invite.code, invite);
}
resolve(invites);
})
.catch(reject);
this.rest.makeRequest('get', Constants.Endpoints.guildInvites(guild.id), true).then(inviteItems => {
const invites = new Collection();
for (const inviteItem of inviteItems) {
const invite = new Invite(this.rest.client, inviteItem);
invites.set(invite.code, invite);
}
resolve(invites);
}).catch(reject);
});
}
}

View File

@@ -3,6 +3,9 @@
* @private
*/
class RequestHandler {
/**
* @param {RESTManager} restManager The REST manager to use
*/
constructor(restManager) {
/**
* The RESTManager that instantiated this RequestHandler
@@ -12,7 +15,7 @@ class RequestHandler {
/**
* A list of requests that have yet to be processed.
* @type {Array<APIRequest>}
* @type {APIRequest[]}
*/
this.queue = [];
}
@@ -31,7 +34,7 @@ class RequestHandler {
/**
* Push a new API request into this bucket
* @param {APIRequest} request the new request to push into the queue
* @param {APIRequest} request The new request to push into the queue
*/
push(request) {
this.queue.push(request);

View File

@@ -8,7 +8,10 @@ const RequestHandler = require('./RequestHandler');
* @private
*/
class SequentialRequestHandler extends RequestHandler {
/**
* @param {RESTManager} restManager The REST manager to use
* @param {string} endpoint The endpoint to handle
*/
constructor(restManager, endpoint) {
super(restManager, endpoint);
@@ -39,8 +42,8 @@ class SequentialRequestHandler extends RequestHandler {
/**
* Performs a request then resolves a promise to indicate its readiness for a new request
* @param {APIRequest} item the item to execute
* @returns {Promise<Object, Error>}
* @param {APIRequest} item The item to execute
* @returns {Promise<?Object|Error>}
*/
execute(item) {
return new Promise(resolve => {
@@ -88,9 +91,8 @@ class SequentialRequestHandler extends RequestHandler {
handle() {
super.handle();
if (this.waiting || this.queue.length === 0 || this.globalLimit) {
return;
}
if (this.waiting || this.queue.length === 0 || this.globalLimit) return;
this.waiting = true;
const item = this.queue[0];

View File

@@ -33,9 +33,7 @@ class ClientVoiceManager {
*/
_checkPendingReady(guildID) {
const pendingRequest = this.pending.get(guildID);
if (!pendingRequest) {
throw new Error('Guild not pending');
}
if (!pendingRequest) throw new Error('Guild not pending');
if (pendingRequest.token && pendingRequest.sessionID && pendingRequest.endpoint) {
const { channel, token, sessionID, endpoint, resolve, reject } = pendingRequest;
const voiceConnection = new VoiceConnection(this, channel, token, sessionID, endpoint, resolve, reject);
@@ -49,15 +47,13 @@ class ClientVoiceManager {
/**
* Called when the Client receives information about this voice server update.
* @param {string} guildID the ID of the Guild
* @param {string} token the token to authorise with
* @param {string} endpoint the endpoint to connect to
* @param {string} guildID The ID of the Guild
* @param {string} token The token to authorise with
* @param {string} endpoint The endpoint to connect to
*/
_receivedVoiceServer(guildID, token, endpoint) {
const pendingRequest = this.pending.get(guildID);
if (!pendingRequest) {
throw new Error('Guild not pending');
}
if (!pendingRequest) throw new Error('Guild not pending');
pendingRequest.token = token;
// remove the port otherwise it errors ¯\_(ツ)_/¯
pendingRequest.endpoint = endpoint.match(/([^:]*)/)[0];
@@ -66,22 +62,20 @@ class ClientVoiceManager {
/**
* Called when the Client receives information about the voice state update.
* @param {string} guildID the ID of the Guild
* @param {string} sessionID the session id to authorise with
* @param {string} guildID The ID of the Guild
* @param {string} sessionID The session id to authorise with
*/
_receivedVoiceStateUpdate(guildID, sessionID) {
const pendingRequest = this.pending.get(guildID);
if (!pendingRequest) {
throw new Error('Guild not pending');
}
if (!pendingRequest) throw new Error('Guild not pending');
pendingRequest.sessionID = sessionID;
this._checkPendingReady(guildID);
}
/**
* Sends a request to the main gateway to join a voice channel
* @param {VoiceChannel} channel the channel to join
* @param {Object} [options] the options to provide
* @param {VoiceChannel} channel The channel to join
* @param {Object} [options] The options to provide
*/
_sendWSJoin(channel, options = {}) {
options = mergeDefault({
@@ -98,14 +92,12 @@ class ClientVoiceManager {
/**
* Sets up a request to join a voice channel
* @param {VoiceChannel} channel the voice channel to join
* @param {VoiceChannel} channel The voice channel to join
* @returns {Promise<VoiceConnection>}
*/
joinChannel(channel) {
return new Promise((resolve, reject) => {
if (this.pending.get(channel.guild.id)) {
throw new Error('already connecting to a channel in this guild');
}
if (this.pending.get(channel.guild.id)) throw new Error('already connecting to a channel in this guild');
const existingConn = this.connections.get(channel.guild.id);
if (existingConn) {
if (existingConn.channel.id !== channel.id) {

View File

@@ -71,14 +71,14 @@ class VoiceConnection extends EventEmitter {
/**
* Executed whenever an error occurs with the UDP/WebSocket sub-client
* @private
* @param {Error} err The error that occurred
* @param {Error} err The encountered error
*/
_onError(err) {
this._reject(err);
/**
* Emitted whenever the connection encounters a fatal error.
* @event VoiceConnection#error
* @param {Error} error the encountered error
* @param {Error} error The encountered error
*/
this.emit('error', err);
this._shutdown(err);
@@ -86,7 +86,7 @@ class VoiceConnection extends EventEmitter {
/**
* Disconnects the Client from the Voice Channel
* @param {string} [reason='user requested'] the reason of the disconnection
* @param {string} [reason='user requested'] The reason of the disconnection
*/
disconnect(reason = 'user requested') {
this.manager.client.ws.send({
@@ -107,19 +107,15 @@ class VoiceConnection extends EventEmitter {
}
_shutdown(e) {
if (!this.ready) {
return;
}
if (!this.ready) return;
this.ready = false;
this.websocket._shutdown();
this.player._shutdown();
if (this.udp) {
this.udp._shutdown();
}
if (this.udp) this.udp._shutdown();
/**
* Emit once the voice connection has disconnected.
* @event VoiceConnection#disconnected
* @param {Error} error the error, if any
* @param {Error} error The encountered error, if any
*/
this.emit('disconnected', e);
}
@@ -149,9 +145,7 @@ class VoiceConnection extends EventEmitter {
});
this.once('ready', () => {
setImmediate(() => {
for (const item of this.queue) {
this.emit(...item);
}
for (const item of this.queue) this.emit(...item);
this.queue = [];
});
});
@@ -197,21 +191,18 @@ class VoiceConnection extends EventEmitter {
/**
* Emitted whenever a user starts/stops speaking
* @event VoiceConnection#speaking
* @param {User} user the user that has started/stopped speaking
* @param {boolean} speaking whether or not the user is speaking
* @param {User} user The user that has started/stopped speaking
* @param {boolean} speaking Whether or not the user is speaking
*/
if (this.ready) {
this.emit('speaking', user, data.speaking);
} else {
this.queue.push(['speaking', user, data.speaking]);
}
if (this.ready) this.emit('speaking', user, data.speaking);
else this.queue.push(['speaking', user, data.speaking]);
guild._memberSpeakUpdate(data.user_id, data.speaking);
});
}
/**
* Play the given file in the voice connection
* @param {string} file the path to the file
* @param {string} file The path to the file
* @returns {StreamDispatcher}
* @example
* // play files natively
@@ -227,7 +218,7 @@ class VoiceConnection extends EventEmitter {
/**
* Plays and converts an audio stream in the voice connection
* @param {ReadableStream} stream the audio stream to play
* @param {ReadableStream} stream The audio stream to play
* @returns {StreamDispatcher}
* @example
* // play streams using ytdl-core
@@ -245,7 +236,7 @@ class VoiceConnection extends EventEmitter {
/**
* Plays a stream of 16-bit signed stereo PCM at 48KHz.
* @param {ReadableStream} stream the audio stream to play.
* @param {ReadableStream} stream The audio stream to play.
* @returns {StreamDispatcher}
*/
playConvertedStream(stream) {

View File

@@ -4,7 +4,6 @@ const Constants = require('../../util/Constants');
const EventEmitter = require('events').EventEmitter;
class VoiceConnectionUDPClient extends EventEmitter {
constructor(voiceConnection, data) {
super();
this.voiceConnection = voiceConnection;
@@ -38,9 +37,7 @@ class VoiceConnectionUDPClient extends EventEmitter {
try {
this.udpSocket.close();
} catch (err) {
if (err.message !== 'Not running') {
this.emit('error', err);
}
if (err.message !== 'Not running') this.emit('error', err);
}
this.udpSocket = null;
}
@@ -55,10 +52,7 @@ class VoiceConnectionUDPClient extends EventEmitter {
this.udpSocket.once('message', message => {
const packet = new Buffer(message);
this.localIP = '';
for (let i = 4; i < packet.indexOf(0, i); i++) {
this.localIP += String.fromCharCode(packet[i]);
}
for (let i = 4; i < packet.indexOf(0, i); i++) this.localIP += String.fromCharCode(packet[i]);
this.localPort = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10);
this.voiceConnection.websocket.send({
@@ -77,7 +71,6 @@ class VoiceConnectionUDPClient extends EventEmitter {
this.udpSocket.on('error', (error, message) => {
this.emit('error', { error, message });
});
this.udpSocket.on('close', error => {
this.emit('close', error);
});
@@ -86,7 +79,6 @@ class VoiceConnectionUDPClient extends EventEmitter {
blankMessage.writeUIntBE(this.data.ssrc, 0, 4);
this.send(blankMessage);
}
}
module.exports = VoiceConnectionUDPClient;

View File

@@ -26,15 +26,11 @@ class VoiceConnectionWebSocket extends EventEmitter {
}
send(data) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
if (this.ws.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(data));
}
_shutdown() {
if (this.ws) {
this.ws.close();
}
if (this.ws) this.ws.close();
clearInterval(this.heartbeat);
}
@@ -97,9 +93,7 @@ class VoiceConnectionWebSocket extends EventEmitter {
case Constants.VoiceOPCodes.SESSION_DESCRIPTION:
this.encryptionMode = packet.d.mode;
this.secretKey = new Uint8Array(new ArrayBuffer(packet.d.secret_key.length));
for (const index in packet.d.secret_key) {
this.secretKey[index] = packet.d.secret_key[index];
}
for (const index in packet.d.secret_key) this.secretKey[index] = packet.d.secret_key[index];
this.emit('ready', this.secretKey);
break;
case Constants.VoiceOPCodes.SPEAKING:

View File

@@ -34,10 +34,10 @@ class StreamDispatcher extends EventEmitter {
}
/**
* Emitted when the dispatcher starts/stops speaking
* @event StreamDispatcher#speaking
* @param {boolean} value whether or not the dispatcher is speaking
*/
* Emitted when the dispatcher starts/stops speaking
* @event StreamDispatcher#speaking
* @param {boolean} value Whether or not the dispatcher is speaking
*/
_setSpeaking(value) {
this.speaking = value;
this.emit('speaking', value);
@@ -62,27 +62,18 @@ class StreamDispatcher extends EventEmitter {
packetBuffer.copy(nonce, 0, 0, 12);
buffer = NaCl.secretbox(buffer, nonce, this.player.connection.data.secret);
for (let i = 0; i < buffer.length; i++) {
packetBuffer[i + 12] = buffer[i];
}
for (let i = 0; i < buffer.length; i++) packetBuffer[i + 12] = buffer[i];
return packetBuffer;
}
_applyVolume(buffer) {
if (this._volume === 1) {
return buffer;
}
if (this._volume === 1) return buffer;
const out = new Buffer(buffer.length);
for (let i = 0; i < buffer.length; i += 2) {
if (i >= buffer.length - 1) {
break;
}
if (i >= buffer.length - 1) break;
const uint = Math.min(32767, Math.max(-32767, Math.floor(this._volume * buffer.readInt16LE(i))));
out.writeInt16LE(uint, i);
}
@@ -95,20 +86,24 @@ class StreamDispatcher extends EventEmitter {
this._setSpeaking(false);
return;
}
const data = this.streamingData;
if (data.missed >= 5) {
this._triggerTerminalState('error', new Error('stream is not generating fast enough'));
return;
}
if (this.paused) {
data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0;
this.player.connection.manager.client.setTimeout(() => this._send(), data.length * 10);
return;
}
const bufferLength = 1920 * data.channels;
this._setSpeaking(true);
let buffer = this.stream.read(bufferLength);
this._setSpeaking(true);
const bufferLength = 1920 * data.channels;
let buffer = this.stream.read(bufferLength);
if (!buffer) {
data.missed++;
this.player.connection.manager.client.setTimeout(() => this._send(), data.length * 10);
@@ -132,7 +127,6 @@ class StreamDispatcher extends EventEmitter {
this._sendBuffer(buffer, data.sequence, data.timestamp);
const nextTime = data.startTime + (data.count * data.length);
this.player.connection.manager.client.setTimeout(() => this._send(), data.length + (nextTime - Date.now()));
} catch (e) {
this._triggerTerminalState('error', e);
@@ -140,33 +134,31 @@ class StreamDispatcher extends EventEmitter {
}
/**
* Emitted once the stream has ended. Attach a `once` listener to this.
* @event StreamDispatcher#end
*/
* Emitted once the stream has ended. Attach a `once` listener to this.
* @event StreamDispatcher#end
*/
_triggerEnd() {
this.emit('end');
}
/**
* Emitted once the stream has encountered an error. Attach a `once` listener to this. Also emits `end`.
* @event StreamDispatcher#error
* @param {Error} err the error encountered
*/
* Emitted once the stream has encountered an error. Attach a `once` listener to this. Also emits `end`.
* @event StreamDispatcher#error
* @param {Error} err The encountered error
*/
_triggerError(err) {
this.emit('end');
this.emit('error', err);
}
_triggerTerminalState(state, err) {
if (this._triggered) {
return;
}
if (this._triggered) return;
/**
* Emitted when the stream wants to give debug information.
* @event StreamDispatcher#debug
* @param {string} information the debug information
*/
* Emitted when the stream wants to give debug information.
* @event StreamDispatcher#debug
* @param {string} information The debug information
*/
this.emit('debug', `triggered terminal state ${state} - stream is now dead`);
this._triggered = true;
this._setSpeaking(false);
@@ -188,17 +180,20 @@ class StreamDispatcher extends EventEmitter {
this.emit('error', 'no stream');
return;
}
this.stream.on('end', err => this._triggerTerminalState('end', err));
this.stream.on('error', err => this._triggerTerminalState('error', err));
const data = this.streamingData;
data.length = 20;
data.missed = 0;
data.startTime = Date.now();
this.stream.once('readable', () => this._send());
}
_pause(value) {
if (value) {
_setPaused(paused) {
if (paused) {
this.paused = true;
this._setSpeaking(false);
} else {
@@ -225,7 +220,7 @@ class StreamDispatcher extends EventEmitter {
/**
* Sets the volume relative to the input stream - i.e. 1 is normal, 0.5 is half, 2 is double.
* @param {number} volume the volume that you want to set
* @param {number} volume The volume that you want to set
*/
setVolume(volume) {
this._volume = volume;
@@ -233,7 +228,7 @@ class StreamDispatcher extends EventEmitter {
/**
* Set the volume in decibels
* @param {number} db the decibels
* @param {number} db The decibels
*/
setVolumeDecibels(db) {
this._volume = Math.pow(10, db / 20);
@@ -241,7 +236,7 @@ class StreamDispatcher extends EventEmitter {
/**
* Set the volume so that a perceived value of 0.5 is half the perceived volume etc.
* @param {number} value the value for the volume
* @param {number} value The value for the volume
*/
setVolumeLogarithmic(value) {
this._volume = Math.pow(value, 1.660964);
@@ -251,14 +246,14 @@ class StreamDispatcher extends EventEmitter {
* Stops sending voice packets to the voice connection (stream may still progress however)
*/
pause() {
this._pause(true);
this._setPaused(true);
}
/**
* Resumes sending voice packets to the voice connection (may be further on in the stream than when paused)
*/
resume() {
this._pause(false);
this._setPaused(false);
}
}

View File

@@ -6,7 +6,6 @@ class NodeOpusEngine extends OpusEngine {
constructor(player) {
super(player);
try {
// eslint-disable-next-line import/no-unresolved
opus = require('node-opus');
} catch (err) {
throw err;

View File

@@ -3,10 +3,6 @@ const list = [
require('./OpusScriptEngine'),
];
exports.add = encoder => {
list.push(encoder);
};
function fetch(Encoder) {
try {
return new Encoder();
@@ -15,12 +11,14 @@ function fetch(Encoder) {
}
}
exports.add = encoder => {
list.push(encoder);
};
exports.fetch = () => {
for (const encoder of list) {
const success = fetch(encoder);
if (success) {
return success;
}
const fetched = fetch(encoder);
if (fetched) return fetched;
}
throw new Error('could not find an opus engine');
};

View File

@@ -1,17 +1,16 @@
const OpusEngine = require('./BaseOpusEngine');
let Opusscript;
let OpusScript;
class NodeOpusEngine extends OpusEngine {
constructor(player) {
super(player);
try {
// eslint-disable-next-line import/no-unresolved
Opusscript = require('opusscript');
OpusScript = require('opusscript');
} catch (err) {
throw err;
}
this.encoder = new Opusscript(48000, 2);
this.encoder = new OpusScript(48000, 2);
}
encode(buffer) {

View File

@@ -1,7 +1,6 @@
const EventEmitter = require('events').EventEmitter;
class ConverterEngine extends EventEmitter {
constructor(player) {
super();
this.player = player;
@@ -10,7 +9,6 @@ class ConverterEngine extends EventEmitter {
createConvertStream() {
return;
}
}
module.exports = ConverterEngine;

View File

@@ -1,15 +1,6 @@
const ConverterEngine = require('./ConverterEngine');
const ChildProcess = require('child_process');
function chooseCommand() {
for (const cmd of ['ffmpeg', 'avconv', './ffmpeg', './avconv']) {
if (!ChildProcess.spawnSync(cmd, ['-h']).error) {
return cmd;
}
}
return null;
}
class FfmpegConverterEngine extends ConverterEngine {
constructor(player) {
super(player);
@@ -17,9 +8,7 @@ class FfmpegConverterEngine extends ConverterEngine {
}
handleError(encoder, err) {
if (encoder.destroy) {
encoder.destroy();
}
if (encoder.destroy) encoder.destroy();
this.emit('error', err);
}
@@ -41,4 +30,11 @@ class FfmpegConverterEngine extends ConverterEngine {
}
}
function chooseCommand() {
for (const cmd of ['ffmpeg', 'avconv', './ffmpeg', './avconv']) {
if (!ChildProcess.spawnSync(cmd, ['-h']).error) return cmd;
}
return null;
}
module.exports = FfmpegConverterEngine;

View File

@@ -5,7 +5,6 @@ const StreamDispatcher = require('../dispatcher/StreamDispatcher');
const EventEmitter = require('events').EventEmitter;
class VoiceConnectionPlayer extends EventEmitter {
constructor(connection) {
super();
this.connection = connection;
@@ -38,9 +37,7 @@ class VoiceConnectionPlayer extends EventEmitter {
_shutdown() {
this.speaking = false;
for (const stream of this.processMap.keys()) {
this.killStream(stream);
}
for (const stream of this.processMap.keys()) this.killStream(stream);
}
killStream(stream) {
@@ -79,9 +76,7 @@ class VoiceConnectionPlayer extends EventEmitter {
}
setSpeaking(value) {
if (this.speaking === value) {
return;
}
if (this.speaking === value) return;
this.speaking = value;
this.connection.websocket.send({
op: Constants.VoiceOPCodes.SPEAKING,
@@ -100,7 +95,6 @@ class VoiceConnectionPlayer extends EventEmitter {
this.dispatcher = dispatcher;
return dispatcher;
}
}
module.exports = VoiceConnectionPlayer;

View File

@@ -2,7 +2,6 @@ const BasePlayer = require('./BasePlayer');
const fs = require('fs');
class DefaultPlayer extends BasePlayer {
playFile(file) {
return this.playStream(fs.createReadStream(file));
}

View File

@@ -11,10 +11,8 @@ class VoiceReadable extends Readable {
return;
}
$push(d) {
if (this.open) {
this.push(d);
}
_push(d) {
if (this.open) this.push(d);
}
}

View File

@@ -53,7 +53,7 @@ class VoiceReceiver extends EventEmitter {
/**
* Creates a readable stream for a user that provides opus data while the user is speaking. When the user
* stops speaking, the stream is destroyed.
* @param {UserResolvable} user the user to create the stream for
* @param {UserResolvable} user The user to create the stream for
* @returns {ReadableStream}
*/
createOpusStream(user) {
@@ -72,17 +72,13 @@ class VoiceReceiver extends EventEmitter {
/**
* Creates a readable stream for a user that provides PCM data while the user is speaking. When the user
* stops speaking, the stream is destroyed. The stream is 16-bit signed stereo PCM at 48KHz.
* @param {UserResolvable} user the user to create the stream for
* @param {UserResolvable} user The user to create the stream for
* @returns {ReadableStream}
*/
createPCMStream(user) {
user = this.connection.manager.client.resolver.resolveUser(user);
if (!user) {
throw new Error('invalid user object supplied');
}
if (this.pcmStreams.get(user.id)) {
throw new Error('there is already an existing stream for that user!');
}
if (!user) throw new Error('invalid user object supplied');
if (this.pcmStreams.get(user.id)) throw new Error('there is already an existing stream for that user!');
const stream = new Readable();
this.pcmStreams.set(user.id, stream);
return stream;
@@ -95,34 +91,30 @@ class VoiceReceiver extends EventEmitter {
/**
* Emitted whenever a voice packet cannot be decrypted
* @event VoiceReceiver#warn
* @param {string} message the warning message
* @param {string} message The warning message
*/
this.emit('warn', 'failed to decrypt voice packet');
return;
}
data = new Buffer(data);
/**
* Emitted whenever voice data is received from the voice connection. This is _always_ emitted (unlike PCM).
* @event VoiceReceiver#opus
* @param {User} user the user that is sending the buffer (is speaking)
* @param {Buffer} buffer the opus buffer
*/
if (this.opusStreams.get(user.id)) {
this.opusStreams.get(user.id).$push(data);
}
if (this.opusStreams.get(user.id)) this.opusStreams.get(user.id)._push(data);
/**
* Emitted whenever voice data is received from the voice connection. This is _always_ emitted (unlike PCM).
* @event VoiceReceiver#opus
* @param {User} user The user that is sending the buffer (is speaking)
* @param {Buffer} buffer The opus buffer
*/
this.emit('opus', user, data);
if (this.listenerCount('pcm') > 0 || this.pcmStreams.size > 0) {
/**
* Emits decoded voice data when it's received. For performance reasons, the decoding will only
* happen if there is at least one `pcm` listener on this receiver.
* @event VoiceReceiver#pcm
* @param {User} user the user that is sending the buffer (is speaking)
* @param {Buffer} buffer the decoded buffer
*/
* Emits decoded voice data when it's received. For performance reasons, the decoding will only
* happen if there is at least one `pcm` listener on this receiver.
* @event VoiceReceiver#pcm
* @param {User} user The user that is sending the buffer (is speaking)
* @param {Buffer} buffer The decoded buffer
*/
const pcm = this.connection.player.opusEncoder.decode(data);
if (this.pcmStreams.get(user.id)) {
this.pcmStreams.get(user.id).$push(pcm);
}
if (this.pcmStreams.get(user.id)) this.pcmStreams.get(user.id)._push(pcm);
this.emit('pcm', user, pcm);
}
}

View File

@@ -8,14 +8,12 @@ const PacketManager = require('./packets/WebSocketPacketManager');
* @private
*/
class WebSocketManager {
constructor(client) {
/**
* The Client that instantiated this WebSocketManager
* @type {Client}
*/
this.client = client;
this.ws = null;
/**
* A WebSocket Packet manager, it handles all the messages
* @type {PacketManager}
@@ -26,43 +24,40 @@ class WebSocketManager {
* @type {number}
*/
this.status = Constants.Status.IDLE;
/**
* The session ID of the connection, null if not yet available.
* @type {?string}
*/
this.sessionID = null;
/**
* The packet count of the client, null if not yet available.
* @type {?number}
*/
this.sequence = -1;
/**
* The gateway address for this WebSocket connection, null if not yet available.
* @type {?string}
*/
this.gateway = null;
/**
* Whether READY was emitted normally (all packets received) or not
* @type {boolean}
*/
this.normalReady = false;
}
/**
* Connects the client to a given gateway
* @param {string} gateway the gateway to connect to
*/
connect(gateway) {
this.normalReady = false;
this.status = Constants.Status.CONNECTING;
/**
* The WebSocket connection to the gateway
* @type {?WebSocket}
*/
this.ws = null;
}
/**
* Connects the client to a given gateway
* @param {string} gateway The gateway to connect to
*/
connect(gateway) {
this.normalReady = false;
this.status = Constants.Status.CONNECTING;
this.ws = new WebSocket(gateway);
this.ws.onopen = () => this.eventOpen();
this.ws.onclose = (d) => this.eventClose(d);
@@ -113,15 +108,12 @@ class WebSocketManager {
* Run whenever the gateway connections opens up
*/
eventOpen() {
if (this.reconnecting) {
this._sendResume();
} else {
this._sendNewIdentify();
}
if (this.reconnecting) this._sendResume();
else this._sendNewIdentify();
}
/**
* Sends a gatway resume packet, in cases of unexpected disconnections.
* Sends a gateway resume packet, in cases of unexpected disconnections.
*/
_sendResume() {
const payload = {
@@ -155,30 +147,23 @@ class WebSocketManager {
/**
* Run whenever the connection to the gateway is closed, it will try to reconnect the client.
* @param {Object} event the event
* @param {Object} event The received websocket data
*/
eventClose(event) {
if (event.code === 4004) {
throw Constants.Errors.BAD_LOGIN;
}
if (!this.reconnecting && event.code !== 1000) {
this.tryReconnect();
}
if (event.code === 4004) throw Constants.Errors.BAD_LOGIN;
if (!this.reconnecting && event.code !== 1000) this.tryReconnect();
}
/**
* Run whenever a message is received from the WebSocket. Returns `true` if the message
* was handled properly.
* @param {Object} event the received websocket data
* @param {Object} event The received websocket data
* @returns {boolean}
*/
eventMessage(event) {
let packet;
try {
if (event.binary) {
event.data = zlib.inflateSync(event.data).toString();
}
if (event.binary) event.data = zlib.inflateSync(event.data).toString();
packet = JSON.parse(event.data);
} catch (e) {
return this.eventError(Constants.Errors.BAD_WS_MESSAGE);
@@ -186,22 +171,19 @@ class WebSocketManager {
this.client.emit('raw', packet);
if (packet.op === 10) {
this.client.manager.setupKeepAlive(packet.d.heartbeat_interval);
}
if (packet.op === 10) this.client.manager.setupKeepAlive(packet.d.heartbeat_interval);
return this.packetManager.handle(packet);
}
/**
* Run whenever an error occurs with the WebSocket connection. Tries to reconnect
* @param {Error} err the error that occurred
* @param {Error} err The encountered error
*/
eventError(err) {
/**
* Emitted whenever the Client encounters a serious connection error
* @event Client#error
* @param {Error} error the encountered error
* @param {Error} error The encountered error
*/
this.client.emit('error', err);
this.tryReconnect();
@@ -210,7 +192,6 @@ class WebSocketManager {
_emitReady(normal = true) {
/**
* Emitted when the Client becomes ready to start working
*
* @event Client#ready
*/
this.status = Constants.Status.READY;
@@ -252,10 +233,9 @@ class WebSocketManager {
this.ws.close();
this.packetManager.handleQueue();
/**
* Emitted when the Client tries to reconnect after being disconnected
*
* @event Client#reconnecting
*/
* Emitted when the Client tries to reconnect after being disconnected
* @event Client#reconnecting
*/
this.client.emit(Constants.Events.RECONNECTING);
this.connect(this.client.ws.gateway);
}

View File

@@ -10,7 +10,6 @@ const BeforeReadyWhitelist = [
];
class WebSocketPacketManager {
constructor(websocketManager) {
this.ws = websocketManager;
this.handlers = {};
@@ -62,9 +61,7 @@ class WebSocketPacketManager {
}
setSequence(s) {
if (s && s > this.ws.sequence) {
this.ws.sequence = s;
}
if (s && s > this.ws.sequence) this.ws.sequence = s;
}
handle(packet) {
@@ -93,13 +90,9 @@ class WebSocketPacketManager {
}
}
if (this.handlers[packet.t]) {
return this.handlers[packet.t].handle(packet);
}
if (this.handlers[packet.t]) return this.handlers[packet.t].handle(packet);
return false;
}
}
module.exports = WebSocketPacketManager;

View File

@@ -1,5 +1,4 @@
class AbstractHandler {
constructor(packetManager) {
this.packetManager = packetManager;
}

View File

@@ -3,25 +3,18 @@ const AbstractHandler = require('./AbstractHandler');
const Constants = require('../../../../util/Constants');
class ChannelCreateHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
const response = client.actions.ChannelCreate.handle(data);
if (response.channel) {
client.emit(Constants.Events.CHANNEL_CREATE, response.channel);
}
if (response.channel) client.emit(Constants.Events.CHANNEL_CREATE, response.channel);
}
}
/**
* Emitted whenever a Channel is created.
*
* @event Client#channelCreate
* @param {Channel} channel The channel that was created
*/
* Emitted whenever a Channel is created.
* @event Client#channelCreate
* @param {Channel} channel The channel that was created
*/
module.exports = ChannelCreateHandler;

View File

@@ -3,25 +3,18 @@ const AbstractHandler = require('./AbstractHandler');
const Constants = require('../../../../util/Constants');
class ChannelDeleteHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
const response = client.actions.ChannelDelete.handle(data);
if (response.channel) {
client.emit(Constants.Events.CHANNEL_DELETE, response.channel);
}
if (response.channel) client.emit(Constants.Events.CHANNEL_DELETE, response.channel);
}
}
/**
* Emitted whenever a Channel is deleted.
*
* @event Client#channelDelete
* @param {Channel} channel The channel that was deleted
*/
* Emitted whenever a Channel is deleted.
* @event Client#channelDelete
* @param {Channel} channel The channel that was deleted
*/
module.exports = ChannelDeleteHandler;

View File

@@ -8,31 +8,24 @@ const Constants = require('../../../../util/Constants');
d:
{ last_pin_timestamp: '2016-08-28T17:37:13.171774+00:00',
channel_id: '314866471639044027' } }
*/
*/
class ChannelPinsUpdate extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
const channel = client.channels.get(data.channel_id);
const time = new Date(data.last_pin_timestamp);
if (channel && time) {
client.emit(Constants.Events.CHANNEL_PINS_UPDATE, channel, time);
}
if (channel && time) client.emit(Constants.Events.CHANNEL_PINS_UPDATE, channel, time);
}
}
/**
* Emitted whenever the pins of a Channel are updated. Due to the nature of the WebSocket event, not much information
* can be provided easily here - you need to manually check the pins yourself.
*
* @event Client#channelPinsUpdate
* @param {Channel} channel The channel that the pins update occured in
* @param {Date} time the time of the pins update
*/
* Emitted whenever the pins of a Channel are updated. Due to the nature of the WebSocket event, not much information
* can be provided easily here - you need to manually check the pins yourself.
* @event Client#channelPinsUpdate
* @param {Channel} channel The channel that the pins update occured in
* @param {Date} time The time of the pins update
*/
module.exports = ChannelPinsUpdate;

View File

@@ -1,14 +1,11 @@
const AbstractHandler = require('./AbstractHandler');
class ChannelUpdateHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
client.actions.ChannelUpdate.handle(data);
}
}
module.exports = ChannelUpdateHandler;

View File

@@ -4,27 +4,20 @@ const AbstractHandler = require('./AbstractHandler');
const Constants = require('../../../../util/Constants');
class GuildBanAddHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
const guild = client.guilds.get(data.guild_id);
const user = client.users.get(data.user.id);
if (guild && user) {
client.emit(Constants.Events.GUILD_BAN_ADD, guild, user);
}
if (guild && user) client.emit(Constants.Events.GUILD_BAN_ADD, guild, user);
}
}
/**
* Emitted whenever a member is banned from a guild.
*
* @event Client#guildBanAdd
* @param {Guild} guild The guild that the ban occurred in
* @param {User} user The user that was banned
*/
* Emitted whenever a member is banned from a guild.
* @event Client#guildBanAdd
* @param {Guild} guild The guild that the ban occurred in
* @param {User} user The user that was banned
*/
module.exports = GuildBanAddHandler;

View File

@@ -3,22 +3,18 @@
const AbstractHandler = require('./AbstractHandler');
class GuildBanRemoveHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
client.actions.GuildBanRemove.handle(data);
}
}
/**
* Emitted whenever a member is unbanned from a guild.
*
* @event Client#guildBanRemove
* @param {Guild} guild The guild that the unban occurred in
* @param {User} user The user that was unbanned
*/
* Emitted whenever a member is unbanned from a guild.
* @event Client#guildBanRemove
* @param {Guild} guild The guild that the unban occurred in
* @param {User} user The user that was unbanned
*/
module.exports = GuildBanRemoveHandler;

View File

@@ -1,13 +1,11 @@
const AbstractHandler = require('./AbstractHandler');
class GuildCreateHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
const guild = client.guilds.get(data.id);
if (guild) {
if (!guild.available && !data.unavailable) {
// a newly available guild
@@ -19,7 +17,6 @@ class GuildCreateHandler extends AbstractHandler {
client.dataManager.newGuild(data);
}
}
}
module.exports = GuildCreateHandler;

View File

@@ -2,25 +2,18 @@ const AbstractHandler = require('./AbstractHandler');
const Constants = require('../../../../util/Constants');
class GuildDeleteHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
const response = client.actions.GuildDelete.handle(data);
if (response.guild) {
client.emit(Constants.Events.GUILD_DELETE, response.guild);
}
if (response.guild) client.emit(Constants.Events.GUILD_DELETE, response.guild);
}
}
/**
* Emitted whenever a Guild is deleted/left.
*
* @event Client#guildDelete
* @param {Guild} guild The guild that was deleted
*/
* Emitted whenever a Guild is deleted/left.
* @event Client#guildDelete
* @param {Guild} guild The guild that was deleted
*/
module.exports = GuildDeleteHandler;

View File

@@ -3,19 +3,15 @@
const AbstractHandler = require('./AbstractHandler');
class GuildMemberAddHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
const guild = client.guilds.get(data.guild_id);
if (guild) {
guild.memberCount++;
guild._addMember(data);
}
}
}
module.exports = GuildMemberAddHandler;

View File

@@ -3,14 +3,11 @@
const AbstractHandler = require('./AbstractHandler');
class GuildMemberRemoveHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
client.actions.GuildMemberRemove.handle(data);
}
}
module.exports = GuildMemberRemoveHandler;

View File

@@ -3,21 +3,16 @@
const AbstractHandler = require('./AbstractHandler');
class GuildMemberUpdateHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
const guild = client.guilds.get(data.guild_id);
if (guild) {
const member = guild.members.get(data.user.id);
if (member) {
guild._updateMember(member, data);
}
if (member) guild._updateMember(member, data);
}
}
}
module.exports = GuildMemberUpdateHandler;

View File

@@ -4,31 +4,26 @@ const AbstractHandler = require('./AbstractHandler');
const Constants = require('../../../../util/Constants');
class GuildMembersChunkHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
const guild = client.guilds.get(data.guild_id);
const members = [];
if (guild) {
for (const member of data.members) {
members.push(guild._addMember(member, true));
}
for (const member of data.members) members.push(guild._addMember(member, true));
}
guild._checkChunks();
client.emit(Constants.Events.GUILD_MEMBERS_CHUNK, guild, members);
}
}
/**
* Emitted whenever a chunk of Guild members is received
*
* @event Client#guildMembersChunk
* @param {Guild} guild The guild that the chunks relate to
* @param {Array<GuildMember>} members The members in the chunk
*/
* Emitted whenever a chunk of Guild members is received
* @event Client#guildMembersChunk
* @param {Guild} guild The guild that the chunks relate to
* @param {GuildMember[]} members The members in the chunk
*/
module.exports = GuildMembersChunkHandler;

View File

@@ -1,14 +1,11 @@
const AbstractHandler = require('./AbstractHandler');
class GuildRoleCreateHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
client.actions.GuildRoleCreate.handle(data);
}
}
module.exports = GuildRoleCreateHandler;

View File

@@ -1,14 +1,11 @@
const AbstractHandler = require('./AbstractHandler');
class GuildRoleDeleteHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
client.actions.GuildRoleDelete.handle(data);
}
}
module.exports = GuildRoleDeleteHandler;

View File

@@ -1,14 +1,11 @@
const AbstractHandler = require('./AbstractHandler');
class GuildRoleUpdateHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
client.actions.GuildRoleUpdate.handle(data);
}
}
module.exports = GuildRoleUpdateHandler;

View File

@@ -1,14 +1,11 @@
const AbstractHandler = require('./AbstractHandler');
class GuildSyncHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
client.actions.GuildSync.handle(data);
}
}
module.exports = GuildSyncHandler;

View File

@@ -1,14 +1,11 @@
const AbstractHandler = require('./AbstractHandler');
class GuildUpdateHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
client.actions.GuildUpdate.handle(data);
}
}
module.exports = GuildUpdateHandler;

View File

@@ -2,25 +2,18 @@ const AbstractHandler = require('./AbstractHandler');
const Constants = require('../../../../util/Constants');
class MessageCreateHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
const response = client.actions.MessageCreate.handle(data);
if (response.message) {
client.emit(Constants.Events.MESSAGE_CREATE, response.message);
}
if (response.message) client.emit(Constants.Events.MESSAGE_CREATE, response.message);
}
}
/**
* Emitted whenever a message is created
*
* @event Client#message
* @param {Message} message The created message
*/
* Emitted whenever a message is created
* @event Client#message
* @param {Message} message The created message
*/
module.exports = MessageCreateHandler;

View File

@@ -2,25 +2,18 @@ const AbstractHandler = require('./AbstractHandler');
const Constants = require('../../../../util/Constants');
class MessageDeleteHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
const response = client.actions.MessageDelete.handle(data);
if (response.message) {
client.emit(Constants.Events.MESSAGE_DELETE, response.message);
}
if (response.message) client.emit(Constants.Events.MESSAGE_DELETE, response.message);
}
}
/**
* Emitted whenever a message is deleted
*
* @event Client#messageDelete
* @param {Message} message The deleted message
*/
* Emitted whenever a message is deleted
* @event Client#messageDelete
* @param {Message} message The deleted message
*/
module.exports = MessageDeleteHandler;

View File

@@ -1,21 +1,17 @@
const AbstractHandler = require('./AbstractHandler');
class MessageDeleteBulkHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
client.actions.MessageDeleteBulk.handle(data);
}
}
/**
* Emitted whenever a messages are deleted in bulk
*
* @event Client#messageDeleteBulk
* @param {Collection<string, Message>} messages The deleted messages, mapped by their ID
*/
* Emitted whenever messages are deleted in bulk
* @event Client#messageDeleteBulk
* @param {Collection<string, Message>} messages The deleted messages, mapped by their ID
*/
module.exports = MessageDeleteBulkHandler;

View File

@@ -1,14 +1,11 @@
const AbstractHandler = require('./AbstractHandler');
class MessageUpdateHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
client.actions.MessageUpdate.handle(data);
}
}
module.exports = MessageUpdateHandler;

View File

@@ -3,21 +3,16 @@ const Constants = require('../../../../util/Constants');
const cloneObject = require('../../../../util/CloneObject');
class PresenceUpdateHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
let user = client.users.get(data.user.id);
const guild = client.guilds.get(data.guild_id);
function makeUser(newUser) {
return client.dataManager.newUser(newUser);
}
// step 1
if (!user) {
if (data.user.username) {
user = makeUser(data.user);
user = client.dataManager.newUser(data.user);
} else {
return;
}
@@ -39,7 +34,6 @@ class PresenceUpdateHandler extends AbstractHandler {
data.user.username = data.user.username || user.username;
data.user.id = data.user.id || user.id;
data.user.discriminator = data.user.discriminator || user.discriminator;
// comment out avatar patching as it causes bugs (see #297)
// data.user.avatar = data.user.avatar || user.avatar;
data.user.status = data.status || user.status;
@@ -58,24 +52,20 @@ class PresenceUpdateHandler extends AbstractHandler {
client.emit(Constants.Events.PRESENCE_UPDATE, oldUser, user);
}
}
}
/**
* Emitted whenever a user changes one of their details or starts/stop playing a game
*
* @event Client#presenceUpdate
* @param {User} oldUser the user before the presence update
* @param {User} newUser the user after the presence update
*/
* Emitted whenever a user changes one of their details or starts/stop playing a game
* @event Client#presenceUpdate
* @param {User} oldUser The user before the presence update
* @param {User} newUser The user after the presence update
*/
/**
* Emitted whenever a member becomes available in a large Guild
*
* @event Client#guildMemberAvailable
* @param {Guild} guild The guild that the member became available in
* @param {GuildMember} member the member that became available
*/
* Emitted whenever a member becomes available in a large Guild
* @event Client#guildMemberAvailable
* @param {Guild} guild The guild that the member became available in
* @param {GuildMember} member The member that became available
*/
module.exports = PresenceUpdateHandler;

View File

@@ -4,40 +4,28 @@ const getStructure = name => require(`../../../../structures/${name}`);
const ClientUser = getStructure('ClientUser');
class ReadyHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
const clientUser = new ClientUser(client, data.user);
client.user = clientUser;
client.readyTime = Date.now();
client.users.set(clientUser.id, clientUser);
for (const guild of data.guilds) {
client.dataManager.newGuild(guild);
}
for (const privateDM of data.private_channels) {
client.dataManager.newChannel(privateDM);
}
if (!client.user.bot) {
client.setInterval(client.syncGuilds.bind(client), 30000);
}
for (const guild of data.guilds) client.dataManager.newGuild(guild);
for (const privateDM of data.private_channels) client.dataManager.newChannel(privateDM);
if (!client.user.bot) client.setInterval(client.syncGuilds.bind(client), 30000);
client.once('ready', client.syncGuilds.bind(client));
client.setTimeout(() => {
if (!client.ws.normalReady) {
client.ws._emitReady(false);
}
if (!client.ws.normalReady) client.ws._emitReady(false);
}, 1200 * data.guilds.length);
this.packetManager.ws.sessionID = data.session_id;
this.packetManager.ws.checkIfReady();
}
}
module.exports = ReadyHandler;

View File

@@ -1,28 +1,10 @@
const AbstractHandler = require('./AbstractHandler');
const Constants = require('../../../../util/Constants');
class TypingData {
constructor(since, lastTimestamp, _timeout) {
this.since = since;
this.lastTimestamp = lastTimestamp;
this._timeout = _timeout;
}
resetTimeout(_timeout) {
clearTimeout(this._timeout);
this._timeout = _timeout;
}
get elapsedTime() {
return Date.now() - this.since;
}
}
class TypingStartHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
const channel = client.channels.get(data.channel_id);
const user = client.users.get(data.user_id);
const timestamp = new Date(data.timestamp * 1000);
@@ -46,23 +28,37 @@ class TypingStartHandler extends AbstractHandler {
}
}
}
}
class TypingData {
constructor(since, lastTimestamp, _timeout) {
this.since = since;
this.lastTimestamp = lastTimestamp;
this._timeout = _timeout;
}
resetTimeout(_timeout) {
clearTimeout(this._timeout);
this._timeout = _timeout;
}
get elapsedTime() {
return Date.now() - this.since;
}
}
/**
* Emitted whenever a user starts typing in a channel
*
* @event Client#typingStart
* @param {Channel} channel the channel the user started typing in
* @param {User} user the user that started typing
*/
* Emitted whenever a user starts typing in a channel
* @event Client#typingStart
* @param {Channel} channel The channel the user started typing in
* @param {User} user The user that started typing
*/
/**
* Emitted whenever a user stops typing in a channel
*
* @event Client#typingStop
* @param {Channel} channel the channel the user stopped typing in
* @param {User} user the user that stopped typing
*/
* Emitted whenever a user stops typing in a channel
* @event Client#typingStop
* @param {Channel} channel The channel the user stopped typing in
* @param {User} user The user that stopped typing
*/
module.exports = TypingStartHandler;

View File

@@ -1,14 +1,11 @@
const AbstractHandler = require('./AbstractHandler');
class UserUpdateHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
client.actions.UserUpdate.handle(data);
}
}
module.exports = UserUpdateHandler;

View File

@@ -9,16 +9,13 @@ const AbstractHandler = require('./AbstractHandler');
*/
class VoiceServerUpdate extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const data = packet.d;
if (client.voice.pending.has(data.guild_id)) {
client.voice._receivedVoiceServer(data.guild_id, data.token, data.endpoint);
}
}
}
module.exports = VoiceServerUpdate;

View File

@@ -4,12 +4,11 @@ const Constants = require('../../../../util/Constants');
const cloneObject = require('../../../../util/CloneObject');
class VoiceStateUpdateHandler extends AbstractHandler {
handle(packet) {
const data = packet.d;
const client = this.packetManager.client;
const guild = client.guilds.get(data.guild_id);
const data = packet.d;
const guild = client.guilds.get(data.guild_id);
if (guild) {
const member = guild.members.get(data.user_id);
if (member) {
@@ -19,18 +18,14 @@ class VoiceStateUpdateHandler extends AbstractHandler {
}
// if the member left the voice channel, unset their speaking property
if (!data.channel_id) {
member.speaking = null;
}
if (!data.channel_id) member.speaking = null;
if (client.voice.pending.has(guild.id) && member.user.id === client.user.id && data.channel_id) {
client.voice._receivedVoiceStateUpdate(data.guild_id, data.session_id);
}
const newChannel = client.channels.get(data.channel_id);
if (newChannel) {
newChannel.members.set(member.user.id, member);
}
if (newChannel) newChannel.members.set(member.user.id, member);
member.serverMute = data.mute;
member.serverDeaf = data.deaf;
@@ -42,15 +37,13 @@ class VoiceStateUpdateHandler extends AbstractHandler {
}
}
}
}
/**
* Emitted whenever a user changes voice state - e.g. joins/leaves a channel, mutes/unmutes.
*
* @event Client#voiceStateUpdate
* @param {GuildMember} oldMember the member before the voice state update
* @param {GuildMember} newMember the member before the voice state update
*/
* Emitted whenever a user changes voice state - e.g. joins/leaves a channel, mutes/unmutes.
* @event Client#voiceStateUpdate
* @param {GuildMember} oldMember The member before the voice state update
* @param {GuildMember} newMember The member after the voice state update
*/
module.exports = VoiceStateUpdateHandler;

View File

@@ -10,9 +10,7 @@ class Channel {
this.client = client;
this.typingMap = {};
this.typingTimeouts = [];
if (guild) {
this.guild = guild;
}
if (guild) this.guild = guild;
/**
* The type of the channel, either:
* * `dm` - a DM channel
@@ -22,9 +20,7 @@ class Channel {
* @type {string}
*/
this.type = null;
if (data) {
this.setup(data);
}
if (data) this.setup(data);
}
setup(data) {

View File

@@ -25,7 +25,7 @@ class ClientUser extends User {
* 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} username The new username
* @returns {Promise<ClientUser>}
* @example
* // set username
@@ -40,7 +40,7 @@ class ClientUser extends User {
/**
* If this user is a "self bot" or logged in using a normal user's details (which should be avoided), you can set the
* email here.
* @param {string} email the new email
* @param {string} email The new email
* @returns {Promise<ClientUser>}
* @example
* // set email
@@ -55,7 +55,7 @@ class ClientUser extends User {
/**
* If this user is a "self bot" or logged in using a normal user's details (which should be avoided), you can set the
* password here.
* @param {string} password the new password
* @param {string} password The new password
* @returns {Promise<ClientUser>}
* @example
* // set password
@@ -69,7 +69,7 @@ class ClientUser extends User {
/**
* Set the avatar of the logged in Client.
* @param {Base64Resolvable} avatar the new avatar
* @param {Base64Resolvable} avatar The new avatar
* @returns {Promise<ClientUser>}
* @example
* // set avatar
@@ -83,9 +83,9 @@ class ClientUser extends User {
/**
* Set the status and playing game of the logged in client.
* @param {string} [status] the status, can be `online` or `idle`.
* @param {string|Object} [game] the game that is being played
* @returns {Promise<ClientUser, Error>}
* @param {string} [status] The status, can be `online` or `idle`
* @param {string|Object} [game] The game that is being played
* @returns {Promise<ClientUser>}
* @example
* // set status
* client.user.setStatus('status', 'game')

View File

@@ -1,6 +1,6 @@
const User = require('./User');
const Channel = require('./Channel');
const TextBasedChannel = require('./interface/TextBasedChannel');
const User = require('./User');
const Collection = require('../util/Collection');
/**

View File

@@ -1,5 +1,5 @@
const Collection = require('../util/Collection');
const Constants = require('../util/Constants');
const Collection = require('../util/Collection');
/**
* Represents a Custom Emoji
@@ -44,16 +44,14 @@ class Emoji {
}
/**
* A collection of roles this emoji is active for (empty if all). Mapped by role ID.
* 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.roleIDS) {
if (this.guild.roles.get(role)) {
roles.set(role, this.guild.roles.get(role));
}
if (this.guild.roles.get(role)) roles.set(role, this.guild.roles.get(role));
}
return roles;
}

View File

@@ -28,31 +28,18 @@ class EvaluatedPermissions {
for (const permissionName in Constants.PermissionFlags) {
serializedPermissions[permissionName] = this.hasPermission(permissionName);
}
return serializedPermissions;
}
/**
* Checks whether a user has a certain permission, e.g. `READ_MESSAGES`.
* @param {string} permission the permission to check for
* @param {boolean} [explicit=false] whether the user should explicitly have the permission.
* 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) {
if (typeof permission === 'string') {
permission = Constants.PermissionFlags[permission];
}
if (!permission) {
throw Constants.Errors.NOT_A_PERMISSION;
}
if (!explicit) {
if ((this.permissions & Constants.PermissionFlags.ADMINISTRATOR) > 0) {
return true;
}
}
permission = this.member.client.resolver.resolvePermission(permission);
if (!explicit && (this.permissions & Constants.PermissionFlags.ADMINISTRATOR) > 0) return true;
return (this.permissions & permission) > 0;
}
}

View File

@@ -1,6 +1,7 @@
const Channel = require('./Channel');
const TextBasedChannel = require('./interface/TextBasedChannel');
const Collection = require('../util/Collection');
const arraysEqual = require('../util/ArraysEqual');
/*
{ type: 3,
@@ -24,49 +25,31 @@ const Collection = require('../util/Collection');
icon: null }
*/
function arraysEqual(a, b) {
if (a === b) return true;
if (a.length !== b.length) return false;
for (const itemInd in a) {
const item = a[itemInd];
const ind = b.indexOf(item);
if (ind) {
b.splice(ind, 1);
}
}
return b.length === 0;
}
/**
* Represents a Group DM on Discord
* @extends {Channel}
* @implements {TextBasedChannel}
*/
class GroupDMChannel extends Channel {
constructor(client, data) {
super(client, data);
this.messages = new Collection();
}
equals(other) {
const base = other &&
const equal = other &&
this.id === other.id &&
this.name === other.name &&
this.icon === other.icon &&
this.owner.id === other.owner_id;
if (base) {
if (equal) {
const thisIDs = this.recipients.array().map(r => r.id);
const otherIDs = other.recipients.map(r => r.id);
return arraysEqual(thisIDs, otherIDs);
}
return base;
return equal;
}
setup(data) {
@@ -86,6 +69,7 @@ class GroupDMChannel extends Channel {
this.recipients.set(user.id, user);
}
}
/**
* The name of this Group DM, can be null if one isn't set.
* @type {string}

View File

@@ -1,25 +1,11 @@
const User = require('./User');
const Role = require('./Role');
const Emoji = require('./Emoji');
const GuildMember = require('./GuildMember');
const Constants = require('../util/Constants');
const cloneObject = require('../util/CloneObject');
const Role = require('./Role');
const Collection = require('../util/Collection');
const Emoji = require('./Emoji');
function arraysEqual(a, b) {
if (a === b) return true;
if (a.length !== b.length) return false;
for (const itemInd in a) {
const item = a[itemInd];
const ind = b.indexOf(item);
if (ind) {
b.splice(ind, 1);
}
}
return b.length === 0;
}
const cloneObject = require('../util/CloneObject');
const arraysEqual = require('../util/ArraysEqual');
/**
* Represents a Guild (or a Server) on Discord.
@@ -50,9 +36,7 @@ class Guild {
*/
this.roles = new Collection();
if (!data) {
return;
}
if (!data) return;
if (data.unavailable) {
/**
@@ -100,12 +84,11 @@ class Guild {
this.channels.get(voiceState.channel_id).members.set(member.user.id, member);
}
/**
* Emitted whenever a user joins a guild.
*
* @event Client#guildMemberAdd
* @param {Guild} guild the guild that the user has joined
* @param {GuildMember} member the member that has joined
*/
* Emitted whenever a user joins a guild.
* @event Client#guildMemberAdd
* @param {Guild} guild The guild that the user has joined
* @param {GuildMember} member The member that has joined
*/
if (this.client.ws.status === Constants.Status.READY && !noEvent) {
this.client.emit(Constants.Events.GUILD_MEMBER_ADD, this, member);
}
@@ -117,25 +100,22 @@ class Guild {
_updateMember(member, data) {
const oldMember = cloneObject(member);
if (data.roles) {
member._roles = data.roles;
} else {
member.nickname = data.nick;
}
if (data.roles) member._roles = data.roles;
else 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 {Guild} guild the guild that the update affects
* @param {GuildMember} oldMember the member before the update
* @param {GuildMember} newMember the member after the update
*/
* Emitted whenever a Guild Member changes - i.e. new role, removed role, nickname
* @event Client#guildMemberUpdate
* @param {Guild} guild The guild that the update affects
* @param {GuildMember} oldMember The member before the update
* @param {GuildMember} newMember The member after the update
*/
this.client.emit(Constants.Events.GUILD_MEMBER_UPDATE, this, oldMember, member);
}
return {
old: oldMember,
mem: member,
@@ -163,7 +143,7 @@ class Guild {
/**
* 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.
* @param {UserResolvable} user The user that you want to obtain the GuildMember of
* @returns {GuildMember|null}
* @example
* // get the guild member of a user
@@ -177,11 +157,11 @@ class Guild {
* 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 the guild to compare
* @param {Guild} guild The guild to compare
* @returns {boolean}
*/
equals(guild) {
let base =
let equal =
guild &&
this.id === guild.id &&
this.available === !guild.unavailable &&
@@ -196,17 +176,15 @@ class Guild {
this.verificationLevel === guild.verification_level &&
this.embedEnabled === guild.embed_enabled;
if (base) {
if (equal) {
if (this.embedChannel) {
if (this.embedChannel.id !== guild.embed_channel_id) {
base = false;
}
if (this.embedChannel.id !== guild.embed_channel_id) equal = false;
} else if (guild.embed_channel_id) {
base = false;
equal = false;
}
}
return base;
return equal;
}
_memberSpeakUpdate(user, speaking) {
@@ -216,8 +194,8 @@ class Guild {
/**
* 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
* @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);
}
@@ -225,7 +203,7 @@ class Guild {
/**
* Sets up the Guild
* @param {*} data the raw data of the guild
* @param {*} data The raw data of the guild
* @private
*/
setup(data) {
@@ -264,12 +242,12 @@ class Guild {
this.icon = data.icon;
/**
* An array of guild features.
* @type {Array<Object>}
* @type {Object[]}
*/
this.features = data.features;
/**
* An array of guild emojis.
* @type {Array<Object>}
* @type {Object[]}
*/
this.emojis = new Collection();
for (const emoji of data.emojis) {
@@ -351,6 +329,7 @@ class Guild {
}
}
}
/**
* The date at which the logged-in client joined the guild.
* @type {Date}
@@ -361,9 +340,9 @@ class Guild {
/**
* 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`.
* @returns {Promise<TextChannel|VoiceChannel, Error>}
* @param {string} name The name of the new channel
* @param {string} type The type of the new channel, either `text` or `voice`
* @returns {Promise<TextChannel|VoiceChannel>}
* @example
* // create a new text channel
* guild.createChannel('new general', 'text')
@@ -376,7 +355,7 @@ class Guild {
/**
* Creates a new role in the guild, as of now this is just a blank role.
* @returns {Promise<Role, Error>}
* @returns {Promise<Role>}
* @example
* // create a new role
* guild.createRole()
@@ -389,7 +368,7 @@ class Guild {
/**
* Causes the Client to leave the guild.
* @returns {Promise<Guild, Error>}
* @returns {Promise<Guild>}
* @example
* // leave a guild
* guild.leave()
@@ -402,7 +381,7 @@ class Guild {
/**
* Causes the Client to delete the guild.
* @returns {Promise<Guild, Error>}
* @returns {Promise<Guild>}
* @example
* // delete a guild
* guild.delete()
@@ -415,8 +394,8 @@ class 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, Error>}
* @param {GuildEditData} data The data to update the guild with
* @returns {Promise<Guild>}
* @example
* // set the guild name and region
* guild.edit({
@@ -432,8 +411,8 @@ class Guild {
/**
* Edit the name of the Guild.
* @param {string} name the new name of the Guild.
* @returns {Promise<Guild, Error>}
* @param {string} name The new name of the Guild
* @returns {Promise<Guild>}
* @example
* // edit the guild name
* guild.setName('Discord Guild')
@@ -446,8 +425,8 @@ class Guild {
/**
* Edit the region of the Guild.
* @param {Region} region the new region of the guild.
* @returns {Promise<Guild, Error>}
* @param {Region} region The new region of the guild.
* @returns {Promise<Guild>}
* @example
* // edit the guild region
* guild.setRegion('london')
@@ -460,8 +439,8 @@ class Guild {
/**
* Edit the verification level of the Guild.
* @param {VerificationLevel} verificationLevel the new verification level of the guild.
* @returns {Promise<Guild, Error>}
* @param {VerificationLevel} verificationLevel The new verification level of the guild
* @returns {Promise<Guild>}
* @example
* // edit the guild verification level
* guild.setVerificationLevel(1)
@@ -474,8 +453,8 @@ class Guild {
/**
* Edit the AFK channel of the Guild.
* @param {GuildChannelResolvable} afkChannel the new AFK channel.
* @returns {Promise<Guild, Error>}
* @param {GuildChannelResolvable} afkChannel The new AFK channel
* @returns {Promise<Guild>}
* @example
* // edit the guild AFK channel
* guild.setAFKChannel(channel)
@@ -488,8 +467,8 @@ class Guild {
/**
* 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, Error>}
* @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)
@@ -502,8 +481,8 @@ class Guild {
/**
* Set a new Guild Icon.
* @param {Base64Resolvable} icon the new icon of the guild.
* @returns {Promise<Guild, Error>}
* @param {Base64Resolvable} icon The new icon of the guild
* @returns {Promise<Guild>}
* @example
* // edit the guild icon
* guild.setIcon(fs.readFileSync('./icon.png'))
@@ -516,8 +495,8 @@ class Guild {
/**
* Sets a new owner of the Guild.
* @param {GuildMemberResolvable} owner the new owner of the Guild.
* @returns {Promise<Guild, Error>}
* @param {GuildMemberResolvable} owner The new owner of the Guild
* @returns {Promise<Guild>}
* @example
* // edit the guild owner
* guild.setOwner(guilds.members[0])
@@ -530,8 +509,8 @@ class Guild {
/**
* Set a new Guild Splash Logo.
* @param {Base64Resolvable} splash the new splash screen of the guild.
* @returns {Promise<Guild, Error>}
* @param {Base64Resolvable} splash The new splash screen of the guild
* @returns {Promise<Guild>}
* @example
* // edit the guild splash
* guild.setIcon(fs.readFileSync('./splash.png'))
@@ -544,8 +523,8 @@ class Guild {
/**
* Unbans a member from the Guild
* @param {UserResolvable} member the member to unban
* @returns {Promise<User, Error>}
* @param {UserResolvable} member The member to unban
* @returns {Promise<User>}
* @example
* // unban a member
* guild.unban('123123123123')
@@ -558,7 +537,7 @@ class Guild {
/**
* Fetch a Collection of banned users in this Guild.
* @returns {Promise<Collection<string, User>, Error>}
* @returns {Promise<Collection<string, User>>}
*/
fetchBans() {
return this.client.rest.methods.getGuildBans(this);
@@ -566,7 +545,7 @@ class Guild {
/**
* Fetch a Collection of invites to this Guild. Resolves with a Collection mapping invites by their codes.
* @returns {Promise<Collection<string, Invite>, Error>}
* @returns {Promise<Collection<string, Invite>>}
*/
fetchInvites() {
return this.client.rest.methods.getGuildInvites(this);
@@ -576,7 +555,7 @@ class Guild {
* 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, Error>}
* @returns {Promise<Guild>}
*/
fetchMembers(query = '') {
return new Promise((resolve, reject) => {
@@ -607,9 +586,7 @@ class Guild {
* @readonly
*/
get iconURL() {
if (!this.icon) {
return null;
}
if (!this.icon) return null;
return Constants.Endpoints.guildIcon(this.id, this.icon);
}

View File

@@ -1,24 +1,10 @@
const Channel = require('./Channel');
const PermissionOverwrites = require('./PermissionOverwrites');
const Role = require('./Role');
const PermissionOverwrites = require('./PermissionOverwrites');
const EvaluatedPermissions = require('./EvaluatedPermissions');
const Constants = require('../util/Constants');
const Collection = require('../util/Collection');
function arraysEqual(a, b) {
if (a === b) return true;
if (a.length !== b.length) return false;
for (const itemInd in a) {
const item = a[itemInd];
const ind = b.indexOf(item);
if (ind) {
b.splice(ind, 1);
}
}
return b.length === 0;
}
const arraysEqual = require('../util/ArraysEqual');
/**
* Represents a Guild Channel (i.e. Text Channels and Voice Channels)
@@ -62,67 +48,57 @@ class GuildChannel extends Channel {
/**
* 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 the channel to compare this channel to
* @param {GuildChannel} channel The channel to compare this channel to
* @returns {boolean}
*/
equals(channel) {
let base = channel &&
let equal = channel &&
this.type === channel.type &&
this.topic === channel.topic &&
this.position === channel.position &&
this.name === channel.name &&
this.id === channel.id;
if (base) {
if (equal) {
if (channel.permission_overwrites) {
const thisIDSet = Array.from(this.permissionOverwrites.keys());
const otherIDSet = channel.permission_overwrites.map(overwrite => overwrite.id);
if (arraysEqual(thisIDSet, otherIDSet)) {
base = true;
} else {
base = false;
}
equal = arraysEqual(thisIDSet, otherIDSet);
} else {
base = false;
equal = false;
}
}
return base;
return equal;
}
/**
* 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
* @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) {
if (this.guild.owner.id === member.id) {
return new EvaluatedPermissions(member, Constants.ALL_PERMISSIONS);
}
if (this.guild.owner.id === member.id) return new EvaluatedPermissions(member, Constants.ALL_PERMISSIONS);
const roles = member.roles;
let permissions = 0;
const overwrites = this.overwritesFor(member, true);
for (const role of roles.values()) {
permissions |= role.permissions;
}
for (const role of roles.values()) permissions |= role.permissions;
for (const overwrite of overwrites.role.concat(overwrites.member)) {
permissions &= ~overwrite.denyData;
permissions |= overwrite.allowData;
}
const admin = Boolean(permissions & (Constants.PermissionFlags.ADMINISTRATOR));
if (admin) {
permissions = Constants.ALL_PERMISSIONS;
}
if (admin) permissions = Constants.ALL_PERMISSIONS;
return new EvaluatedPermissions(member, permissions);
}
return null;
}
@@ -165,9 +141,9 @@ class GuildChannel extends Channel {
/**
* Overwrites the permissions for a user or role in this channel.
* @param {Role|UserResolvable} userOrRole the user or role to update
* @param {PermissionOverwriteOptions} options the configuration for the update
* @returns {Promise<null, Error>}
* @param {Role|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, {
@@ -187,9 +163,7 @@ class GuildChannel extends Channel {
} else {
userOrRole = this.client.resolver.resolveUser(userOrRole);
payload.type = 'member';
if (!userOrRole) {
return Promise.reject('supplied parameter was neither a user or a role');
}
if (!userOrRole) return Promise.reject(new TypeError('supplied parameter was neither a user or a role'));
}
payload.id = userOrRole.id;
@@ -220,7 +194,7 @@ class GuildChannel extends Channel {
/**
* Set a new name for the Guild Channel
* @param {string} name the 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
@@ -234,7 +208,7 @@ class GuildChannel extends Channel {
/**
* Set a new position for the Guild Channel
* @param {number} position the 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
@@ -248,7 +222,7 @@ class GuildChannel extends Channel {
/**
* Set a new topic for the Guild Channel
* @param {string} topic the 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
@@ -288,8 +262,8 @@ class GuildChannel extends Channel {
/**
* Create an invite to this Guild Channel
* @param {InviteOptions} [options={}] the options to provide when creating the invite
* @returns {Promise<Invite, Error>}
* @param {InviteOptions} [options={}] The options for the invite
* @returns {Promise<Invite>}
*/
createInvite(options = {}) {
return this.client.rest.methods.createChannelInvite(this, options);

View File

@@ -23,9 +23,7 @@ class GuildMember {
*/
this.user = {};
this._roles = [];
if (data) {
this.setup(data);
}
if (data) this.setup(data);
}
setup(data) {
@@ -91,15 +89,11 @@ class GuildMember {
const list = new Collection();
const everyoneRole = this.guild.roles.get(this.guild.id);
if (everyoneRole) {
list.set(everyoneRole.id, everyoneRole);
}
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);
}
if (role) list.set(role.id, role);
}
return list;
@@ -143,8 +137,8 @@ class GuildMember {
/**
* Mute/unmute a user
* @param {boolean} mute whether or not the member should be muted
* @returns {Promise<GuildMember, Error>}
* @param {boolean} mute Whether or not the member should be muted
* @returns {Promise<GuildMember>}
*/
setMute(mute) {
return this.edit({ mute });
@@ -152,8 +146,8 @@ class GuildMember {
/**
* Deafen/undeafen a user
* @param {boolean} deaf whether or not the member should be deafened
* @returns {Promise<GuildMember, Error>}
* @param {boolean} deaf Whether or not the member should be deafened
* @returns {Promise<GuildMember>}
*/
setDeaf(deaf) {
return this.edit({ deaf });
@@ -161,8 +155,8 @@ class GuildMember {
/**
* Moves the Guild Member to the given channel.
* @param {ChannelResolvable} channel the channel to move the member to
* @returns {Promise<GuildMember, Error>}
* @param {ChannelResolvable} channel The channel to move the member to
* @returns {Promise<GuildMember>}
*/
setVoiceChannel(channel) {
return this.edit({ channel });
@@ -170,8 +164,8 @@ class GuildMember {
/**
* Sets the Roles applied to the member.
* @param {Collection<string, Role>|Array<Role>} roles the roles to apply
* @returns {Promise<GuildMember, Error>}
* @param {Collection<string, Role>|Role[]} roles The roles to apply
* @returns {Promise<GuildMember>}
*/
setRoles(roles) {
return this.edit({ roles });
@@ -179,8 +173,8 @@ class GuildMember {
/**
* Set the nickname for the Guild Member
* @param {string} nick the nickname for the Guild Member
* @returns {Promise<GuildMember, Error>}
* @param {string} nick The nickname for the Guild Member
* @returns {Promise<GuildMember>}
*/
setNickname(nick) {
return this.edit({ nick });
@@ -188,15 +182,15 @@ class GuildMember {
/**
* Edit a Guild Member
* @param {GuildmemberEditData} data the data to edit the member with
* @returns {Promise<GuildMember, Error>}
* @param {GuildmemberEditData} data The data to edit the member with
* @returns {Promise<GuildMember>}
*/
edit(data) {
return this.client.rest.methods.updateGuildMember(this, data);
}
/**
* Deletes any DM's with this Guild Member
* Deletes any DMs with this Guild Member
* @returns {Promise<DMChannel>}
*/
deleteDM() {
@@ -215,7 +209,7 @@ class GuildMember {
* 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, Error>}
* @returns {Promise<GuildMember>}
* @example
* // ban a guild member
* guildMember.ban(7);

View File

@@ -98,7 +98,7 @@ class Invite {
/**
* Deletes this invite
* @returns {Promise<Invite, Error>}
* @returns {Promise<Invite>}
*/
delete() {
return this.client.rest.methods.deleteInvite(this);

View File

@@ -1,6 +1,7 @@
const Collection = require('../util/Collection');
const Attachment = require('./MessageAttachment');
const Embed = require('./MessageEmbed');
const Collection = require('../util/Collection');
/**
* Represents a Message on Discord
*/
@@ -26,9 +27,7 @@ class Message {
* @type {Client}
*/
this.client = client;
if (data) {
this.setup(data);
}
if (data) this.setup(data);
}
setup(data) {
@@ -69,7 +68,7 @@ class Message {
this.nonce = data.nonce;
/**
* A list of embeds in the message - e.g. YouTube Player
* @type {Array<Embed>}
* @type {Embed[]}
*/
this.embeds = data.embeds.map(e => new Embed(this, e));
/**
@@ -77,17 +76,15 @@ class Message {
* @type {Collection<string, MessageAttachment>}
*/
this.attachments = new Collection();
for (const attachment of data.attachments) {
this.attachments.set(attachment.id, new Attachment(this, attachment));
}
for (const attachment of data.attachments) this.attachments.set(attachment.id, new Attachment(this, attachment));
/**
* 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.
* @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(),
@@ -114,9 +111,7 @@ class Message {
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 (role) this.mentions.roles.set(role.id, role);
}
}
@@ -124,9 +119,7 @@ class Message {
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 (chan) this.mentions.channels.set(chan.id, chan);
}
}
@@ -135,9 +128,7 @@ class Message {
* @type {boolean}
*/
this.system = false;
if (data.type === 6) {
this.system = true;
}
if (data.type === 6) this.system = true;
}
/**
* When the message was sent
@@ -158,31 +149,17 @@ class Message {
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._timestamp = new Date(data.timestamp).getTime();
if (this.guild) this.member = this.guild.member(this.author);
}
if (data.content) this.content = data.content;
if (data.timestamp) this._timestamp = 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 ('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) {
@@ -214,9 +191,7 @@ class Message {
}
}
}
if (data.id) {
this.id = data.id;
}
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) {
@@ -237,14 +212,11 @@ class 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;
if (embedUpdate) {
const base = this.id === message.id &&
this.embeds.length === message.embeds.length;
return base;
}
let base = this.id === message.id &&
let equal = this.id === message.id &&
this.author.id === message.author.id &&
this.content === message.content &&
this.tts === message.tts &&
@@ -252,19 +224,19 @@ class Message {
this.embeds.length === message.embeds.length &&
this.attachments.length === message.attachments.length;
if (base && rawData) {
base = this.mentions.everyone === message.mentions.everyone &&
if (equal && rawData) {
equal = this.mentions.everyone === message.mentions.everyone &&
this._timestamp === new Date(rawData.timestamp).getTime() &&
this._editedTimestamp === new Date(rawData.edited_timestamp).getTime();
}
return base;
return equal;
}
/**
* Deletes the message
* @param {number} [timeout=0] How long to wait to delete the message in milliseconds
* @returns {Promise<Message, Error>}
* @returns {Promise<Message>}
* @example
* // delete a message
* message.delete()
@@ -283,8 +255,8 @@ class Message {
/**
* Edit the content of a message
* @param {string} content the new content of a message
* @returns {Promise<Message, Error>}
* @param {string} content The new content for the message
* @returns {Promise<Message>}
* @example
* // update the content of a message
* message.edit('This is my new content!')
@@ -297,9 +269,9 @@ class Message {
/**
* Reply to a message
* @param {string} content the content of the message
* @param {MessageOptions} [options = {}] the options to provide
* @returns {Promise<Message, Error>}
* @param {string} content The content for the message
* @param {MessageOptions} [options = {}] The options to provide
* @returns {Promise<Message>}
* @example
* // reply to a message
* message.reply('Hey, I'm a reply!')
@@ -313,7 +285,7 @@ class Message {
/**
* Pins this message to the channel's pinned messages
* @returns {Promise<Message, Error>}
* @returns {Promise<Message>}
*/
pin() {
return this.client.rest.methods.pinMessage(this);
@@ -321,7 +293,7 @@ class Message {
/**
* Unpins this message from the channel's pinned messages
* @returns {Promise<Message, Error>}
* @returns {Promise<Message>}
*/
unpin() {
return this.client.rest.methods.unpinMessage(this);

View File

@@ -1,3 +1,59 @@
/**
* Represents an embed in an image - e.g. preview of image
*/
class MessageEmbed {
constructor(message, data) {
/**
* The message this embed is part of
* @type {Message}
*/
this.message = message;
/**
* The client that instantiated this embed
* @type {Client}
*/
this.client = message.client;
this.setup(data);
}
setup(data) {
/**
* The title of this embed, if there is one
* @type {?string}
*/
this.title = data.title;
/**
* The type of this embed
* @type {string}
*/
this.type = data.type;
/**
* 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;
if (data.thumbnail) {
/**
* The thumbnail of this embed, if there is one
* @type {MessageEmbedThumbnail}
*/
this.thumbnail = new MessageEmbedThumbnail(this, data.thumbnail);
}
if (data.provider) {
/**
* The provider of this embed, if there is one
* @type {MessageEmbedProvider}
*/
this.provider = new MessageEmbedProvider(this, data.provider);
}
}
}
/**
* Represents a thumbnail for a Message embed
*/
@@ -62,60 +118,4 @@ class MessageEmbedProvider {
}
}
/**
* Represents an embed in an image - e.g. preview of image
*/
class MessageEmbed {
constructor(message, data) {
/**
* The message this embed is part of
* @type {Message}
*/
this.message = message;
/**
* The client that instantiated this embed
* @type {Client}
*/
this.client = message.client;
this.setup(data);
}
setup(data) {
/**
* The title of this embed, if there is one
* @type {?string}
*/
this.title = data.title;
/**
* The type of this embed
* @type {string}
*/
this.type = data.type;
/**
* 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;
if (data.thumbnail) {
/**
* The thumbnail of this embed, if there is one
* @type {MessageEmbedThumbnail}
*/
this.thumbnail = new MessageEmbedThumbnail(this, data.thumbnail);
}
if (data.provider) {
/**
* The provider of this embed, if there is one
* @type {MessageEmbedProvider}
*/
this.provider = new MessageEmbedProvider(this, data.provider);
}
}
}
module.exports = MessageEmbed;

View File

@@ -8,9 +8,7 @@ class PermissionOverwrites {
* @type {GuildChannel}
*/
this.channel = guildChannel;
if (data) {
this.setup(data);
}
if (data) this.setup(data);
}
setup(data) {
@@ -30,7 +28,7 @@ class PermissionOverwrites {
/**
* Delete this Permission Overwrite.
* @returns {Promise<PermissionOverwrites, Error>}
* @returns {Promise<PermissionOverwrites>}
*/
delete() {
return this.channel.client.rest.methods.deletePermissionOverwrites(this);

View File

@@ -15,22 +15,18 @@ class Role {
* @type {Client}
*/
this.client = guild.client;
if (data) {
this.setup(data);
}
if (data) this.setup(data);
}
equals(role) {
return (
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
);
this.managed === role.managed;
}
setup(data) {
@@ -73,7 +69,7 @@ class Role {
/**
* Deletes the role
* @returns {Promise<Role, Error>}
* @returns {Promise<Role>}
* @example
* // delete a role
* role.delete()
@@ -86,8 +82,8 @@ class Role {
/**
* Edits the role
* @param {RoleData} data the new data for the role
* @returns {Promise<Role, Error>}
* @param {RoleData} data The new data for the role
* @returns {Promise<Role>}
* @example
* // edit a role
* role.edit({name: 'new role'})
@@ -100,8 +96,8 @@ class Role {
/**
* Set a new name for the role
* @param {string} name the new name of the role
* @returns {Promise<Role, Error>}
* @param {string} name The new name of the role
* @returns {Promise<Role>}
* @example
* // set the name of the role
* role.setName('new role')
@@ -114,8 +110,8 @@ class Role {
/**
* 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, Error>}
* @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')
@@ -128,8 +124,8 @@ class Role {
/**
* Set whether or not the role should be hoisted
* @param {boolean} hoist whether or not to hoist the role
* @returns {Promise<Role, Error>}
* @param {boolean} hoist Whether or not to hoist the role
* @returns {Promise<Role>}
* @example
* // set the hoist of the role
* role.setHoist(true)
@@ -142,8 +138,8 @@ class Role {
/**
* Set the position of the role
* @param {number} position the position of the role
* @returns {Promise<Role, Error>}
* @param {number} position The position of the role
* @returns {Promise<Role>}
* @example
* // set the position of the role
* role.setPosition(1)
@@ -156,8 +152,8 @@ class Role {
/**
* Set the permissions of the role
* @param {Array<string>} permissions the permissions of the role
* @returns {Promise<Role, Error>}
* @param {string[]} permissions The permissions of the role
* @returns {Promise<Role>}
* @example
* // set the permissions of the role
* role.setPermissions(['KICK_MEMBERS', 'BAN_MEMBERS'])
@@ -180,14 +176,13 @@ class Role {
for (const permissionName in Constants.PermissionFlags) {
serializedPermissions[permissionName] = this.hasPermission(permissionName);
}
return serializedPermissions;
}
/**
* Whether or not the role includes the given permission
* @param {string} permission the name of the permission to test
* @param {boolean} [explicit=false] whether or not the inclusion of the permission is explicit
* @param {PermissionResolvable} permission The name of the permission to test
* @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
@@ -198,20 +193,8 @@ class Role {
* }
*/
hasPermission(permission, explicit = false) {
if (typeof permission === 'string') {
permission = Constants.PermissionFlags[permission];
}
if (!permission) {
throw Constants.Errors.NOT_A_PERMISSION;
}
if (!explicit) {
if ((this.permissions & Constants.PermissionFlags.ADMINISTRATOR) > 0) {
return true;
}
}
permission = this.client.resolver.resolvePermission(permission);
if (!explicit && (this.permissions & Constants.PermissionFlags.ADMINISTRATOR) > 0) return true;
return (this.permissions & permission) > 0;
}
@@ -230,9 +213,7 @@ class Role {
*/
get hexColor() {
let col = this.color.toString(16);
while (col.length < 6) {
col = `0${col}`;
}
while (col.length < 6) col = `0${col}`;
return `#${col}`;
}
}

View File

@@ -8,7 +8,6 @@ const Collection = require('../util/Collection');
* @implements {TextBasedChannel}
*/
class TextChannel extends GuildChannel {
constructor(guild, data) {
super(guild, data);
this.messages = new Collection();

View File

@@ -8,9 +8,7 @@ const Constants = require('../util/Constants');
class User {
constructor(client, data) {
this.client = client;
if (data) {
this.setup(data);
}
if (data) this.setup(data);
}
setup(data) {
@@ -72,9 +70,7 @@ class User {
* @readonly
*/
get avatarURL() {
if (!this.avatar) {
return null;
}
if (!this.avatar) return null;
return Constants.Endpoints.avatar(this.id, this.avatar);
}
@@ -89,28 +85,23 @@ class User {
/**
* Checks if the user is equal to another. It compares username, ID, discriminator, status and the game being played.
* It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties.
* @param {User} user the user to compare
* @param {User} user The user to compare
* @returns {boolean}
*/
equals(user) {
let base = user &&
let equal = user &&
this.username === user.username &&
this.id === user.id &&
this.discriminator === user.discriminator &&
this.avatar === user.avatar &&
this.bot === Boolean(user.bot);
if (base) {
if (user.status) {
base = this.status === user.status;
}
if (user.game) {
base = this.game === user.game;
}
if (equal) {
if (user.status) equal = this.status === user.status;
if (equal && user.game) equal = this.game === user.game;
}
return base;
return equal;
}
sendMessage() {

View File

@@ -32,7 +32,7 @@ class VoiceChannel extends GuildChannel {
/**
* Sets the bitrate of the channel
* @param {number} bitrate the new bitrate
* @param {number} bitrate The new bitrate
* @returns {Promise<VoiceChannel>}
* @example
* // set the bitrate of a voice channel
@@ -46,7 +46,7 @@ class VoiceChannel extends GuildChannel {
/**
* Attempts to join this Voice Channel
* @returns {Promise<VoiceConnection, Error>}
* @returns {Promise<VoiceConnection>}
* @example
* // join a voice channel
* voiceChannel.join()
@@ -64,12 +64,8 @@ class VoiceChannel extends GuildChannel {
* voiceChannel.leave();
*/
leave() {
const exists = this.client.voice.connections.get(this.guild.id);
if (exists) {
if (exists.channel.id === this.id) {
exists.disconnect();
}
}
const connection = this.client.voice.connections.get(this.guild.id);
if (connection && connection.channel.id === this.id) connection.disconnect();
}
}

View File

@@ -1,128 +1,13 @@
const Collection = require('../../util/Collection');
const Message = require('../Message');
const path = require('path');
const EventEmitter = require('events').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.
* ```js
* {
* time: null, // time in milliseconds. If specified, the collector ends after this amount of time.
* max: null, // the maximum amount of messages to handle before ending.
* }
* ```
* @typedef {Object} CollectorOptions
*/
/**
* Collects messages based on a specified filter, then emits them.
* @extends {EventEmitter}
*/
class MessageCollector extends EventEmitter {
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;
this.listener = message => this.verify(message);
this.channel.client.on('message', this.listener);
/**
* A collection of collected messages, mapped by message ID.
* @type {Collection<string, Message>}
*/
this.collected = new Collection();
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.options.max && this.collected.size === this.options.max) {
this.stop('limit');
}
return true;
}
return false;
}
/**
* 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);
}
}
const Message = require('../Message');
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.
@@ -133,19 +18,16 @@ class TextBasedChannel {
/**
* Bulk delete a given Collection or Array of messages in one go. Returns the deleted messages after.
* @param {Map<string, Message>|Array<Message>} messages the messages to delete
* @param {Collection<string, Message>|Message[]} messages The messages to delete
* @returns {Collection<string, Message>}
*/
bulkDelete(messages) {
if (messages instanceof Map) {
messages = messages.array();
}
if (!(messages instanceof Array)) {
return Promise.reject('pass an array or map');
}
if (messages instanceof Collection) messages = messages.array();
if (!(messages instanceof Array)) return Promise.reject(new TypeError('messages must be an array or collection'));
const messageIDs = messages.map(m => m.id);
return this.client.rest.methods.bulkDeleteMessages(this, messageIDs);
}
/**
* Options that can be passed into sendMessage or sendTTSMessage:
* ```js
@@ -156,10 +38,11 @@ class TextBasedChannel {
* ```
* @typedef {Object} MessageOptions
*/
/**
* Send a message to this channel
* @param {string} content the content to send
* @param {MessageOptions} [options={}] the options to provide
* @param {string} content The content to send
* @param {MessageOptions} [options={}] The options to provide
* @returns {Promise<Message>}
* @example
* // send a message
@@ -170,10 +53,11 @@ class TextBasedChannel {
sendMessage(content, options = {}) {
return this.client.rest.methods.sendMessage(this, content, options.tts, options.nonce);
}
/**
* Send a text-to-speech message to this channel
* @param {string} content the content to send
* @param {MessageOptions} [options={}] the options to provide
* @param {string} content The content to send
* @param {MessageOptions} [options={}] The options to provide
* @returns {Promise<Message>}
* @example
* // send a TTS message
@@ -184,6 +68,7 @@ class TextBasedChannel {
sendTTSMessage(content, options = {}) {
return this.client.rest.methods.sendMessage(this, content, true, options.nonce);
}
/**
* Send a file to this channel
* @param {FileResolvable} attachment The file to send
@@ -201,16 +86,15 @@ class TextBasedChannel {
}
}
return new Promise((resolve, reject) => {
this.client.resolver.resolveFile(attachment)
.then(file => {
this.client.resolver.resolveFile(attachment).then(file => {
this.client.rest.methods.sendMessage(this, undefined, false, undefined, {
file,
name: fileName,
}).then(resolve).catch(reject);
})
.catch(reject);
}).catch(reject);
});
}
/**
* The parameters to pass in when requesting previous messages from a channel. `around`, `before` and
* `after` are mutually exclusive. All the parameters are optional.
@@ -227,8 +111,8 @@ class TextBasedChannel {
/**
* Gets the past messages sent in this channel. Resolves with a Collection mapping message ID's to Message objects.
* @param {ChannelLogsQueryOptions} [options={}] the query parameters to pass in
* @returns {Promise<Collection<string, Message>, Error>}
* @param {ChannelLogsQueryOptions} [options={}] The query parameters to pass in
* @returns {Promise<Collection<string, Message>>}
* @example
* // get messages
* channel.fetchMessages({limit: 10})
@@ -237,17 +121,15 @@ class TextBasedChannel {
*/
fetchMessages(options = {}) {
return new Promise((resolve, reject) => {
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);
}
resolve(messages);
})
.catch(reject);
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);
}
resolve(messages);
}).catch(reject);
});
}
@@ -278,7 +160,7 @@ class TextBasedChannel {
* 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 force the indicator to stop regardless of call count
* @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();
@@ -316,8 +198,8 @@ class TextBasedChannel {
/**
* Creates a Message Collector
* @param {CollectorFilterFunction} filter the filter to create the collector with
* @param {CollectorOptions} [options={}] the options to pass to the 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
@@ -329,8 +211,7 @@ class TextBasedChannel {
* collector.on('end', collected => console.log(`Collected ${collected.size} items`));
*/
createCollector(filter, options = {}) {
const collector = new MessageCollector(this, filter, options);
return collector;
return new MessageCollector(this, filter, options);
}
/**
@@ -346,8 +227,8 @@ class TextBasedChannel {
/**
* 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
* @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
@@ -372,43 +253,140 @@ class TextBasedChannel {
_cacheMessage(message) {
const maxSize = this.client.options.max_message_cache;
if (maxSize === 0) {
// saves on performance
return null;
}
if (this.messages.size >= maxSize) {
this.messages.delete(this.messages.keys().next().value);
}
if (maxSize === 0) return null;
if (this.messages.size >= maxSize) this.messages.delete(this.messages.keys().next().value);
this.messages.set(message.id, message);
return message;
}
/**
* Fetches the pinned messages of this Channel and returns a Collection of them.
* @returns {Promise<Collection<string, Message>, Error>}
* @returns {Promise<Collection<string, Message>>}
*/
fetchPinnedMessages() {
return new Promise((resolve, reject) => {
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);
}
resolve(messages);
})
.catch(reject);
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);
}
resolve(messages);
}).catch(reject);
});
}
}
function applyProp(structure, prop) {
Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop));
/**
* 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.
* ```js
* {
* time: null, // time in milliseconds. If specified, the collector ends after this amount of time.
* max: null, // the maximum amount of messages to handle before ending.
* }
* ```
* @typedef {Object} CollectorOptions
*/
/**
* @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;
this.listener = message => this.verify(message);
this.channel.client.on('message', this.listener);
/**
* A collection of collected messages, mapped by message ID.
* @type {Collection<string, Message>}
*/
this.collected = new Collection();
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.options.max && this.collected.size === this.options.max) this.stop('limit');
return true;
}
return false;
}
/**
* 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);
}
}
exports.applyToClass = (structure, full = false) => {
@@ -425,7 +403,9 @@ exports.applyToClass = (structure, full = false) => {
props.push('createCollector');
props.push('awaitMessages');
}
for (const prop of props) {
applyProp(structure, prop);
}
for (const prop of props) applyProp(structure, prop);
};
function applyProp(structure, prop) {
Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop));
}

14
src/util/ArraysEqual.js Normal file
View File

@@ -0,0 +1,14 @@
module.exports = function arraysEqual(a, b) {
if (a === b) return true;
if (a.length !== b.length) return false;
for (const itemInd in a) {
const item = a[itemInd];
const ind = b.indexOf(item);
if (ind) {
b.splice(ind, 1);
}
}
return b.length === 0;
};

View File

@@ -1,6 +1,5 @@
module.exports = function cloneObject(obj) {
const cloned = Object.create(obj);
Object.assign(cloned, obj);
return cloned;
};

View File

@@ -3,10 +3,9 @@
* @extends {Map}
*/
class Collection extends Map {
/**
* Returns an ordered array of the values of this collection.
* @returns {Array}
* @returns {*[]}
* @example
* // identical to:
* Array.from(collection.values());
@@ -17,18 +16,19 @@ class Collection extends Map {
/**
* Returns the first item in this collection.
* @returns {Object}
* @returns {*}
* @example
* // identical to:
* Array.from(collection.values())[0];
*/
first() {
return this.array()[0];
return this.values().next().value;
}
/**
* Returns the last item in this collection.
* @returns {Object}
* Returns the last item in this collection. This is a relatively slow operation,
* since an array copy of the values must be made to find the last element.
* @returns {*}
*/
last() {
const arr = this.array();
@@ -36,8 +36,9 @@ class Collection extends Map {
}
/**
* Returns a random item from this collection.
* @returns {Object}
* Returns a random item from this collection. This is a relatively slow operation,
* since an array copy of the values must be made to find a random element.
* @returns {*}
*/
random() {
const arr = this.array();
@@ -47,32 +48,21 @@ class Collection extends Map {
/**
* If the items in this collection have a delete method (e.g. messages), invoke
* the delete method. Returns an array of promises
* @returns {Array<Promise>}
* @returns {Promise[]}
*/
deleteAll() {
const returns = [];
for (const item of this.values()) {
if (item.delete) {
returns.push(item.delete());
}
if (item.delete) returns.push(item.delete());
}
return returns;
}
/**
* The length (size) of this collection.
* @readonly
* @type {number}
*/
get length() {
return this.size;
}
/**
* Returns an array of items where `item[key] === value` of the collection
* @param {string} key the key to filter by
* @param {*} value the expected value
* @returns {Array<Object>}
* @param {string} key The key to filter by
* @param {*} value The expected value
* @returns {*[]}
* @example
* collection.getAll('username', 'Bob');
*/
@@ -81,18 +71,16 @@ class Collection extends Map {
if (typeof value === 'undefined') throw new Error('value must be specified');
const results = [];
for (const item of this.values()) {
if (item[key] === value) {
results.push(item);
}
if (item[key] === value) results.push(item);
}
return results;
}
/**
* Returns a single item where `item[key] === value`
* @param {string} key the key to filter by
* @param {*} value the expected value
* @returns {Object}
* @param {string} key The key to filter by
* @param {*} value The expected value
* @returns {*}
* @example
* collection.get('id', '123123...');
*/
@@ -100,17 +88,15 @@ class Collection extends Map {
if (typeof key !== 'string') throw new TypeError('key must be a string');
if (typeof value === 'undefined') throw new Error('value must be specified');
for (const item of this.values()) {
if (item[key] === value) {
return item;
}
if (item[key] === value) return item;
}
return null;
}
/**
* Returns true if the collection has an item where `item[key] === value`
* @param {string} key the key to filter by
* @param {*} value the expected value
* @param {string} key The key to filter by
* @param {*} value The expected value
* @returns {boolean}
* @example
* if (collection.exists('id', '123123...')) {
@@ -121,32 +107,26 @@ class Collection extends Map {
return Boolean(this.find(key, value));
}
_arrayMethod(method, args) {
return Array.prototype[method].apply(this.array(), args);
}
/**
* Identical to
* [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter),
* but returns a Collection instead of an Array.
* @param {function} callback the callback used to filter
* @param {Object} [thisArg] value to set as this when filtering
* @param {function} callback Function used to filter (should return a boolean)
* @param {Object} [thisArg] Value to set as this when filtering
* @returns {Collection}
*/
filter(...args) {
const newArray = this.array().filter(...args);
const collection = new Collection();
for (const item of newArray) {
collection.set(item.id, item);
}
for (const item of newArray) collection.set(item.id, item);
return collection;
}
/**
* Functionally identical shortcut to `collection.array().map(...)`.
* @param {function} callback Function that produces an element of the new Array, taking three arguments.
* @param {function} callback Function that produces an element of the new Array, taking three arguments
* @param {*} [thisArg] Optional. Value to use as this when executing callback.
* @returns {array}
* @returns {*[]}
*/
map(...args) {
return this.array().map(...args);

View File

@@ -67,7 +67,7 @@ exports.Errors = {
NO_BOT_ACCOUNT: new Error('you should ideally be using a bot account!'),
BAD_WS_MESSAGE: new Error('a bad message was received from the websocket - bad compression or not json'),
TOOK_TOO_LONG: new Error('something took too long to do'),
NOT_A_PERMISSION: new Error('that is not a valid permission number'),
NOT_A_PERMISSION: new Error('that is not a valid permission string or number'),
INVALID_RATE_LIMIT_METHOD: new Error('unknown rate limiting method'),
BAD_LOGIN: new Error('incorrect login details were provided'),
};
@@ -235,10 +235,7 @@ const PermissionFlags = exports.PermissionFlags = {
};
let _ALL_PERMISSIONS = 0;
for (const key in PermissionFlags) {
_ALL_PERMISSIONS |= PermissionFlags[key];
}
for (const key in PermissionFlags) _ALL_PERMISSIONS |= PermissionFlags[key];
exports.ALL_PERMISSIONS = _ALL_PERMISSIONS;

View File

@@ -1,10 +1,5 @@
module.exports = function merge(def, given) {
if (!given) {
return def;
}
given = given || {};
if (!given) return def;
for (const key in def) {
if (!{}.hasOwnProperty.call(given, key)) {
given[key] = def[key];