Files
discord.js/index.js
2015-08-22 18:28:31 +01:00

1088 lines
33 KiB
JavaScript

var request = require("superagent");
var Endpoints = require("./lib/endpoints.js");
var Server = require("./lib/server.js").Server;
var Message = require("./lib/message.js").Message;
var User = require("./lib/user.js").User;
var Channel = require("./lib/channel.js").Channel;
var List = require("./lib/list.js").List;
var Invite = require("./lib/invite.js").Invite;
var PMChannel = require("./lib/PMChannel.js").PMChannel;
var WebSocket = require('ws');
var Internal = require("./lib/internal.js").Internal;
var TokenManager = require("./lib/TokenManager.js").TokenManager;
var serverCreateRequests = [],
globalLoginTime = Date.now();
function tp(time) {
return Date.now() - time;
}
/**
* The wrapper module for the Discord Client, also provides some helpful objects.
*
* @module Discord
*/
exports;
exports.Endpoints = Endpoints;
exports.Server = Server;
exports.Message = Message;
exports.User = User;
exports.Channel = Channel;
exports.List = List;
exports.Invite = Invite;
exports.PMChannel = PMChannel;
/**
* The Discord Client used to interface with the Discord API. Instantiate this to start writing code
* with discord.js
* @class Client
* @constructor
* @param {Object} [options] An object containing configurable options.
* @param {Number} [options.maxmessage=5000] The maximum amount of messages to be stored per channel.
*/
exports.Client = function (shouldUseTokenManager) {
/**
* Contains the options of the client
* @attribute options
* @type {Object}
*/
if (shouldUseTokenManager)
this.tokenManager = new TokenManager("./", "tokencache.json");
/**
* Contains the token used to authorise HTTP requests and WebSocket connection. Received when login was successful.
* @attribute token
* @readonly
* @type {String}
*/
this.token = "";
/**
* Indicates whether the client is logged in or not. Does not indicate whether the client is ready.
* @attribute loggedIn
* @readonly
* @type {Boolean}
*/
this.loggedIn = false;
/**
* The WebSocket used when receiving message and other event updates.
* @type {WebSocket}
* @attribute websocket
* @readonly
*/
this.websocket = null;
/**
* An Object containing the functions tied to events. These should be set using Client.on();
* @type {Object}
* @attribute events
*/
this.events = {};
/**
* The User of the Client class, set when the initial startup is complete.
* @attribute user
* @type {User}
* @readonly
*/
this.user = null;
/**
* Indicates whether the Client is ready and has cached all servers it as aware of.
* @type {Boolean}
* @attribute ready
* @readonly
*/
this.ready = false;
/**
* A List containing all the Servers the Client has access to.
* @attribute serverList
* @type {List}
* @readonly
*/
this.serverList = new List("id");
/**
* A List containing all the PMChannels the Client has access to.
* @attribute PMList
* @type {List}
* @readonly
*/
this.PMList = new List("id");
}
/**
* Returns a list of all servers that the Discord Client has access to.
*
* @method getServers
* @return {List} ServerList
*/
exports.Client.prototype.getServers = function () {
return this.serverList;
}
/**
* Returns a list of all servers that the Discord Client has access to.
* @method getChannels
* @return {List} Channelist
*/
exports.Client.prototype.getChannels = function () {
return this.serverList.concatSublists("channels", "id");
}
/**
* Returns a Server matching the given id, or false if not found. Will return false if the server is not cached or not available.
* @method getServer
* @param {String/Number} id The ID of the Server
* @return {Server} The Server matching the ID
*/
exports.Client.prototype.getServer = function (id) {
return this.getServers().filter("id", id, true);
}
/**
* Returns a Channel matching the given id, or false if not found. Will return false if the Channel is not cached or not available.
* @method getChannel
* @param {String/Number} id The ID of the Channel
* @return {Server} The Channel matching the ID
*/
exports.Client.prototype.getChannel = function (id) {
return this.getChannels().filter("id", id, true);
}
/**
* Returns a Channel matching the given name, or false if not found. Will return false if the Channel is not cached or not available.
* @method getChannelByName
* @param {String/Number} name The Name of the Channel
* @return {Server} The Channel matching the Name
*/
exports.Client.prototype.getChannelByName = function (name) {
return this.getChannels().filter("name", name, true);
}
/**
* Triggers an .on() event.
* @param {String} event The event to be triggered
* @param {Array} args The arguments to be passed onto the method
* @return {Boolean} whether the event was triggered successfully.
* @method triggerEvent
* @private
*/
exports.Client.prototype.triggerEvent = function (event, args) {
if (!this.ready && event !== "raw" && event !== "disconnected" && event !== "debug") { //if we're not even loaded yet, don't try doing anything because it always ends badly!
return false;
}
if (this.events[event]) {
this.events[event].apply(this, args);
return true;
} else {
return false;
}
}
/**
* Binds a function to an event
* @param {String} name The event name which the function should be bound to.
* @param {Function} fn The function that should be bound to the event.
* @method on
*/
exports.Client.prototype.on = function (name, fn) {
this.events[name] = fn;
}
/**
* Unbinds a function from an event
* @param {String} name The event name which should be cleared
* @method off
*/
exports.Client.prototype.off = function (name) {
this.events[name] = function () { };
}
exports.Client.prototype.cacheServer = function (id, cb, members) {
var self = this;
var serverInput = {};
if (typeof id === 'string' || id instanceof String) {
//actually an ID
if (this.serverList.filter("id", id).length > 0) {
return;
}
Internal.XHR.getServer(self.token, id, function (err, data) {
if (!err) {
makeServer(data);
}
})
} else {
// got objects because SPEEEDDDD
if (this.serverList.filter("id", id.id).length > 0) {
return;
}
serverInput = id;
id = id.id;
makeServer(serverInput);
}
function channelsFromHTTP() {
Internal.XHR.getChannel(self.token, id, function (err) {
if (!err)
cacheChannels(res.body);
})
}
var server;
function makeServer(dat) {
server = new Server(dat.region, dat.owner_id, dat.name, id, serverInput.members || dat.members, dat.icon, dat.afk_timeout, dat.afk_channel_id);
if (dat.channels)
cacheChannels(dat.channels);
else
channelsFromHTTP();
}
function cacheChannels(dat) {
var channelList = dat;
for (var channel of channelList) {
server.channels.add(new Channel(channel, server));
}
self.serverList.add(server);
cb(server);
}
}
/**
* Logs the Client in with the specified credentials and begins initialising it.
* @async
* @method login
* @param {String} email The Discord email.
* @param {String} password The Discord password.
* @param {Function} [callback] Called when received reply from authentication server.
* @param {Object} callback.error Set to null if there was no error logging in, otherwise is an Object that
* can be evaluated as true.
* @param {String} callback.error.reason The reason why there was an error
* @param {Object} callback.error.error The raw XHR error.
* @param {String} callback.token The token received when logging in
*/
exports.Client.prototype.login = function (email, password, callback, noCache) {
globalLoginTime = Date.now();
this.debug("login called at " + globalLoginTime);
var self = this;
callback = callback || function () { };
if (noCache == undefined || noCache == null) {
noCache = false;
}
self.connectWebsocket();
if (this.tokenManager) {
if (this.tokenManager.exists(email) && !noCache) {
var token = this.tokenManager.getToken(email, password);
if (!token.match(/[^\w.-]+/g)) {
done(this.tokenManager.getToken(email, password));
self.debug("loaded token from caches in " + tp(globalLoginTime));
return;
} else {
self.debug("error getting token from caches, using default auth");
}
}
}
var time = Date.now();
Internal.XHR.login(email, password, function (err, token) {
if (err) {
self.triggerEvent("disconnected", [{
reason: "failed to log in",
error: err
}]);
self.websocket.close();
self.debug("failed to login in " + tp(globalLoginTime));
} else {
if (!noCache) {
self.tokenManager.addToken(email, token, password);
}
self.debug("loaded token from auth servers in " + tp(globalLoginTime));
done(token);
}
});
function done(token) {
self.email = email;
self.password = password;
self.debug("using token " + token);
self.token = token;
self.websocket.sendData();
self.loggedIn = true;
callback(null, token);
}
}
/**
* Replies to a message with a given message
* @param {Message/User/Channel/Server/String} destination Where the message should be sent. Channel IDs can also be used here.
* @param {String/Message/Array} toSend If a message, the message's content will be sent. If an array, a message will be sent of
* the array seperated by a newline. If a String, the string will be sent.
* @param {Function} callback Called when a response from the API has been received, the message has either been sent or not.
* @param {Object} callback.error If there was an error, this would be an XHR Error object. Otherwise, it will be null.
* @param {Message} callback.message If there were no errors, this will be the sent message in Message form.
* @param {Object} options see sendMessage(options)
* @method reply
*/
exports.Client.prototype.reply = function (destination, toSend, callback, options) {
if (toSend instanceof Array) {
toSend = toSend.join("\n");
}
toSend = destination.author.mention() + ", " + toSend;
this.sendMessage(destination, toSend, callback, options);
}
exports.Client.prototype.connectWebsocket = function (cb) {
var self = this;
var sentInitData = false;
this.websocket = new WebSocket(Endpoints.WEBSOCKET_HUB);
this.websocket.onclose = function (e) {
self.triggerEvent("disconnected", [{
reason: "websocket disconnected",
error: e
}]);
};
this.websocket.onmessage = function (e) {
self.triggerEvent("raw", [e]);
var dat = JSON.parse(e.data);
var webself = this;
switch (dat.op) {
case 0:
if (dat.t === "READY") {
self.debug("got ready packet");
var data = dat.d;
self.user = new User(data.user);
var _servers = data.guilds,
servers = [];
var cached = 0,
toCache = _servers.length;
for (x in data.private_channels) {
self.PMList.add(new PMChannel(data.private_channels[x].recipient, data.private_channels[x].id));
}
for (x in _servers) {
_server = _servers[x];
self.cacheServer(_server, function (server) {
cached++;
if (cached === toCache) {
self.ready = true;
self.triggerEvent("ready");
self.debug("ready triggered");
}
});
}
setInterval(function () {
webself.keepAlive.apply(webself);
}, data.heartbeat_interval);
} else if (dat.t === "MESSAGE_CREATE") {
var data = dat.d;
var channel = self.getChannel(data.channel_id);
var message = new Message(data, channel);
self.triggerEvent("message", [message]);
if (channel.messages)
channel.messages.add(message);
} else if (dat.t === "MESSAGE_DELETE") {
var data = dat.d;
var channel = self.getChannel(data.channel_id);
if (!channel.messages)
return;
var _msg = channel.messages.filter("id", data.id, true);
if (_msg) {
self.triggerEvent("messageDelete", [_msg]);
channel.messages.removeElement(_msg);
}
} else if (dat.t === "MESSAGE_UPDATE") {
var data = dat.d;
if (!self.ready)
return;
var formerMessage = false;
var channel = self.getChannel(data.channel_id);
if (channel) {
formerMessage = channel.messages.filter("id", data.id, true);
var newMessage;
data.author = data.author || formerMessage.author;
data.timestamp = data.time || formerMessage.time;
data.content = data.content || formerMessage.content;
data.channel = data.channel || formerMessage.channel;
data.id = data.id || formerMessage.id;
data.mentions = data.mentions || formerMessage.mentions;
data.mention_everyone = data.mention_everyone || formerMessage.everyoneMentioned;
data.embeds = data.embeds || formerMessage.embeds;
try {
newMessage = new Message(data, channel);
} catch (e) {
self.debug("dropped a message update packet");
return;
}
self.triggerEvent("messageUpdate", [formerMessage, newMessage]);
if (formerMessage)
channel.messages.updateElement(formerMessage, newMessage);
else
channel.messages.add(newMessage);
}
} else if (dat.t === "PRESENCE_UPDATE") {
var data = dat.d;
var getUser = self.getUser(data.user.id);
if (getUser) {
//user already exists
var usr = new User(data.user);
if (usr.equalsStrict(getUser)) {
//no changes, actually a presence.
} else {
if (self.updateUserReferences(data.user.id, getUser, new User(data.user))) {
self.triggerEvent("userupdate", [getUser, usr]);
}
}
}
self.triggerEvent("presence", [new User(data.user), data.status, self.serverList.filter("id", data.guild_id, true)]);
} else if (dat.t === "GUILD_DELETE") {
var deletedServer = self.serverList.filter("id", dat.d.id, true);
if (deletedServer) {
self.triggerEvent("serverDelete", [deletedServer]);
}
} else if (dat.t === "CHANNEL_DELETE") {
var delServer = self.serverList.filter("id", dat.d.guild_id, true);
if (delServer) {
var channel = delServer.channels.filter("id", dat.d.id, true);
if (channel) {
self.triggerEvent("channelDelete", [channel]);
}
}
} else if (dat.t === "GUILD_CREATE") {
if (!self.serverList.filter("id", dat.d.id, true)) {
self.cacheServer(dat.d, function (server) {
if (serverCreateRequests[server.id]) {
serverCreateRequests[server.id](null, server);
serverCreateRequests[server.id] = null;
} else {
self.triggerEvent("serverJoin", [server]);
}
});
}
} else if (dat.t === "CHANNEL_CREATE") {
var srv = self.serverList.filter("id", dat.d.guild_id, true);
if (srv) {
if (!srv.channels.filter("id", dat.d.d, true)) {
var chann = new Channel(dat.d, srv);
srv.channels.add(new Channel(dat.d, srv));
self.triggerEvent("channelCreate", [chann]);
}
}
} else if (dat.t === "USER_UPDATE") {
if (dat.d.id === self.user.id) {
var newUsr = new User(dat.d);
self.triggerEvent("userupdate", [self.user, newUsr]);
self.user = newUsr;
}
} else if (dat.t === "GUILD_MEMBER_ADD") {
var srv = self.getServer(dat.d.guild_id);
if (srv) {
var usr = new User(dat.d.user);
srv.members.add(usr);
self.triggerEvent("serverMemberAdd", [usr]);
}
} else if (dat.t === "GUILD_MEMBER_REMOVE") {
var srv = self.getServer(dat.d.guild_id);
if (srv) {
var usr = new User(dat.d.user);
srv.members.removeElement(usr);
self.triggerEvent("serverMemberRemove", [usr]);
}
}
break;
}
};
this.websocket.sendPacket = function (p) {
this.send(JSON.stringify(p));
}
this.websocket.keepAlive = function () {
if (this.readyState !== 1)
return false;
this.sendPacket({
op: 1,
d: Date.now()
});
}
this.websocket.onopen = function () {
self.debug("websocket conn open");
this.sendData("onopen");
}
this.websocket.sendData = function (why) {
if (this.readyState == 1 && !sentInitData && self.token) {
sentInitData = true;
var connDat = {
op: 2,
d: {
token: self.token,
v: 2
}
};
connDat.d.properties = Internal.WebSocket.properties;
this.sendPacket(connDat);
}
}
}
exports.Client.prototype.debug = function (msg) {
this.triggerEvent("debug", ["[SL " + tp(globalLoginTime) + "] " + msg]);
}
/**
* Logs the current Client out of Discord and closes any connections.
* @param {Function} callback Called after a response is obtained.
* @param {Object} callback.error Null unless there was an error, in which case is an XHR error.
* @method logout
*/
exports.Client.prototype.logout = function (callback) {
callback = callback || function () { };
var self = this;
Internal.XHR.logout(self.token, function (err) {
if (err) {
callback(err);
}
self.loggedIn = false;
self.websocket.close();
});
}
/**
* Creates a server with the specified name and region and returns it
* @param {String} name The name of the server
* @param {String} region The region of the server
* @param {Function} callback Called when the request is made.
* @param {Object} callback.error An XHR error or null if there were no errors.
* @param {Server} callback.server A Server object representing the created server.
* @method createServer
* @async
*/
exports.Client.prototype.createServer = function (name, region, cb) {
var self = this;
Internal.XHR.createServer(self.token, name, region, function (err, data) {
if (err) {
cb(err);
} else {
self.cacheServer(data, function (server) {
cb(null, server);
});
}
});
}
/**
* Makes the Client leave the Server
* @param {Server} server A server object. The server you want to leave.
* @param {Function} callback Called when the leave request is made.
* @param {Object} callback.error An XHR error or null if there were no errors.
* @param {Server} callback.server A Server object representing the deleted server.
* @method leaveServer
* @async
*/
exports.Client.prototype.leaveServer = function (server, callback) {
var self = this;
// callback is not necessary for this function
callback = callback || function () { };
Internal.XHR.leaveServer(self.token, server.id, function (err) {
if (err) {
callback(err);
} else {
self.serverList.removeElement(server);
callback(null, server);
}
});
}
/**
* Creates an Invite to the specified channel/server with the specified options
* @param {Channel/Server} channel The channel/server the invite is to be made to.
* @param {Object} [options] The options for the invite
* @param {Number} [options.max_age=0] When the invite will expire in seconds
* @param {Number} [options.max_uses=0] How many uses the invite has
* @param {Boolean} [options.temporary=false] Whether the invite is temporary
* @param {Boolean} [options.xkcdpass=false] Whether the invite's code should be composed of words.
* @param {Function} callback Called when the invite request has been made
* @param {Object} callback.error An XHR Error or null if there were no errors.
* @param {Invite} callback.invite An invite object representing the created invite.
* @method createInvite
*/
exports.Client.prototype.createInvite = function (channel, options, callback) {
var self = this;
var options = options || {};
// callback is not necessary for this function
callback = callback || function () { };
if (channel instanceof Server) {
channel = channel.getDefaultChannel();
}
options.max_age = options.max_age || 0;
options.max_uses = options.max_uses || 0;
options.temporary = options.temporary || false;
options.xkcdpass = options.xkcd || false;
Internal.XHR.createInvite(self.token, channel.id, options, function (err, data) {
if (err) {
callback(err);
} else {
callback(null, new Invite(data));
}
});
}
exports.Client.prototype.startPM = function (user, callback) {
var self = this;
callback = callback || function () { };
Internal.XHR.startPM(self.token, self.user.id, user.id, function (err, data) {
if (err) {
callback(err);
} else {
var channel = new PMChannel(data.recipient, data.id);
self.PMList.add(channel);
callback(null, channel);
}
});
}
/**
* Sends a message to the specified destination.
* @param {Server/Channel/PMChannel/Message/User/String} destination Where the message should be sent. If this is a String, the String should be a channel ID.
* @param {String/Array/Message} toSend The message to send. If an array, the array will be seperated into new lines and then sent.
* @param {Function} callback Called when the message has been sent.
* @param {Object} error An XHR Error or null if there were no errors.
* @param {Message} message A message object representing the sent object.
* @param {Object} [options] An object containing options for the message.
* @param {Array/Boolean/String} [options.mentions=true] If an Array, it should be an array of User IDs. If a boolean, false will
* notify no-one, and true will figure out who should be mentioned based on the message. If a String, should be a User
* ID.
* @param {Number} [options.selfDestruct=false] If specified, should be the amount of milliseconds at which the message should
* delete itself after being sent.
* @method sendMessage
*/
exports.Client.prototype.sendFile = function (destination, toSend, fileName, callback, options) {
this.sendMessage(destination, toSend, callback, options, fileName);
}
exports.Client.prototype.sendMessage = function (destination, toSend, callback, options, fileName) {
options = options || {};
callback = callback || function () { };
var channel_id, message, mentions, self = this;
channel_id = resolveChannel(destination, self);
if (!fileName) {
message = resolveMessage(toSend);
mentions = resolveMentions(message, options.mention);
}
if (channel_id) {
send();
} else {
//a channel is being sorted
}
function send() {
if (fileName) {
Internal.XHR.sendFile(self.token, channel_id, toSend, fileName, function (err) {
callback(err);
});
return;
}
Internal.XHR.sendMessage(self.token, channel_id, {
content: message,
mentions: mentions
}, function (err, data) {
if (err) {
callback(err);
} else {
var msg = new Message(data, self.getChannel(data.channel_id));
if (options.selfDestruct) {
setTimeout(function () {
self.deleteMessage(msg);
}, options.selfDestruct);
}
callback(null, msg);
}
});
}
function setChannId(id) {
channel_id = id;
}
function resolveChannel(destination, self) {
var channel_id = false;
if (destination instanceof Server) {
channel_id = destination.getDefaultChannel().id;
} else if (destination instanceof Channel) {
channel_id = destination.id;
} else if (destination instanceof PMChannel) {
channel_id = destination.id;
} else if (destination instanceof Message) {
channel_id = destination.channel.id;
} else if (destination instanceof User) {
var destId = self.PMList.deepFilter(["user", "id"], destination.id, true);
if (destId) {
channel_id = destId.id;
} else {
//start a PM and then get that use that
self.startPM(destination, function (err, channel) {
if (err) {
callback(err);
} else {
self.PMList.add(new PMChannel(channel.user, channel.id));
setChannId(channel.id);
send();
}
});
return false;
}
} else {
channel_id = destination;
}
return channel_id;
}
function resolveMessage(toSend) {
var message;
if (typeof toSend === "string" || toSend instanceof String)
message = toSend;
else if (toSend instanceof Array)
message = toSend.join("\n");
else if (toSend instanceof Message)
message = toSend.content;
else
message = toSend;
return message.substring(0, 2000);
}
function resolveMentions(message, mentionsOpt) {
var mentions = [];
if (mentionsOpt === false) { } else if (mentionsOpt || mentionsOpt === "auto" || mentionsOpt == null || mentionsOpt == undefined) {
for (mention of (message.match(/<@[^>]*>/g) || [])) {
mentions.push(mention.substring(2, mention.length - 1));
}
} else if (mentionsOpt instanceof Array) {
for (mention of mentionsOpt) {
if (mention instanceof User) {
mentions.push(mention.id);
} else {
mentions.push(mention);
}
}
}
return mentions;
}
}
/**
* Deletes the specified message if the bot has authority
* @param {Message} message The message to delete
* @param {Function} callback Called after the message deletion request is sent.
* @param {Object} callback.error If there was an error, this would be an XHR Error object. Otherwise, it will be null.
* @param {Message} callback.message A message object representing the deleted object.
* @method deleteMessage
*/
exports.Client.prototype.deleteMessage = function (message, callback) {
callback = callback || function () { };
var self = this;
Internal.XHR.deleteMessage(self.token, message.channel.id, message.id, callback);
}
exports.Client.prototype.updateMessage = function (oldMessage, newContent, callback, options) {
var self = this;
var channel = oldMessage.channel;
options = options || {};
Internal.XHR.updateMessage(self.token, channel.id, oldMessage.id, {
content: newContent,
mentions: []
}, function (err, data) {
if (err) {
callback(err);
return;
}
var msg = new Message(data, self.getChannel(data.channel_id));
if (options.selfDestruct) {
setTimeout(function () {
self.deleteMessage(msg);
}, options.selfDestruct);
}
callback(null, msg);
});
}
exports.Client.prototype.setUsername = function (username, callback) {
var self = this;
Internal.XHR.setUsername(self.token, self.user.avatar, self.email, null, self.password, username, function (err) {
callback(err);
});
}
exports.Client.prototype.getChannelLogs = function (channel, amount, callback) {
var self = this;
Internal.XHR.getChannelLogs(self.token, channel.id, (amount || 50), function (err, data) {
if (err) {
callback(err);
return;
}
var logs = new List("id");
for (message of data) {
logs.add(new Message(message, channel));
}
callback(null, logs);
});
}
exports.Client.prototype.createChannel = function (server, name, type, callback) {
var self = this;
Internal.XHR.createChannel(self.token, server.id, name, type, function (err, data) {
if (err) {
callback(err);
} else {
var chann = new Channel(data, server);
server.channels.add(chann);
callback(null, chann);
}
});
}
exports.Client.prototype.deleteChannel = function (channel, callback) {
var self = this;
Internal.XHR.deleteChannel(self.token, channel.id, function (err) {
channel.server.channels.removeElement(channel);
self.triggerEvent("channelDelete", [channel]);
callback(null);
});
}
exports.Client.prototype.deleteServer = function (server, callback) {
var self = this;
Internal.XHR.deleteServer(self.token, server.id, function (err) {
self.serverList.removeElement(server);
self.triggerEvent("serverDelete", [server]);
callback(null);
});
}
exports.Client.prototype.joinServer = function (invite, callback) {
var self = this;
var code = (invite instanceof Invite ? invite.code : invite);
Internal.XHR.acceptInvite(self.token, code, function (err, inviteData) {
if (err) {
callback(err);
} else {
serverCreateRequests[inviteData.guild.id] = callback;
}
});
}
exports.Client.prototype.getServers = function () {
return this.serverList;
}
exports.Client.prototype.getChannels = function () {
return this.serverList.concatSublists("channels", "id");
}
exports.Client.prototype.getUsers = function () {
return this.getServers().concatSublists("members", "id");
}
exports.Client.prototype.getServer = function (id) {
return this.getServers().filter("id", id, true);
}
exports.Client.prototype.getChannel = function (id) {
var normalChan = this.getChannels().filter("id", id, true);
return normalChan || this.PMList.filter("id", id, true);
}
exports.Client.prototype.getChannelByName = function (name) {
return this.getChannels().filter("name", name, true);
}
exports.Client.prototype.getUser = function (id) {
return this.getUsers().filter("id", id, true);
}
exports.isUserID = function (id) {
return ((id + "").length === 17 && !isNaN(id));
}
exports.Client.prototype.updateUserReferences = function (id, oldUser, user) {
if (oldUser.equalsStrict(user)) {
return false;
}
for (server of this.serverList.contents) {
server.members.updateElement(oldUser, user);
}
this.debug("Updated references to " + oldUser.username + " to " + this.getUser(id).username);
return true;
}
exports.Client.prototype.addPM = function (pm) {
this.PMList.add(pm);
}