From f59154273526b2beed4383332a7f9fbe5ed65ab7 Mon Sep 17 00:00:00 2001 From: Nicholas Tay Date: Wed, 30 Mar 2016 21:01:48 +1100 Subject: [PATCH] Working friends support with events and requests --- lib/Client/Client.js | 64 +++++++++++++++++++++++ lib/Client/InternalClient.js | 98 ++++++++++++++++++++++++++++++++++++ lib/Constants.js | 9 +++- src/Client/Client.js | 54 ++++++++++++++++++++ src/Client/InternalClient.js | 95 +++++++++++++++++++++++++++++++++- src/Constants.js | 9 +++- 6 files changed, 324 insertions(+), 5 deletions(-) diff --git a/lib/Client/Client.js b/lib/Client/Client.js index 1f2efe736..806a6ffca 100644 --- a/lib/Client/Client.js +++ b/lib/Client/Client.js @@ -953,6 +953,22 @@ var Client = (function (_EventEmitter) { return this.internal.leaveVoiceChannel().then(dataCallback(callback), errorCallback(callback)); }; + // def addFriend + + Client.prototype.addFriend = function addFriend(user) { + var callback = arguments.length <= 1 || arguments[1] === undefined ? function () /*err, {}*/{} : arguments[1]; + + return this.internal.addFriend(user).then(dataCallback(callback), errorCallback(callback)); + }; + + // def removeFriend + + Client.prototype.removeFriend = function removeFriend(user) { + var callback = arguments.length <= 1 || arguments[1] === undefined ? function () /*err, {}*/{} : arguments[1]; + + return this.internal.removeFriend(user).then(dataCallback(callback), errorCallback(callback)); + }; + // def awaitResponse Client.prototype.awaitResponse = function awaitResponse(msg) { @@ -1093,6 +1109,54 @@ var Client = (function (_EventEmitter) { return this.internal.private_channels; } + /** + * The friends that the Client is aware of. Only available after `ready` event has been emitted. + * @type {Cache|null} a Cache of friend Users (or null if bot account) + * @readonly + * @example + * // log names of the friends that the client is aware of + * for(var user of client.friends){ + * console.log(user.username); + * } + */ + }, { + key: "friends", + get: function get() { + return this.internal.friends; + } + + /** + * The incoming friend requests that the Client is aware of. Only available after `ready` event has been emitted. + * @type {Cache|null} a Cache of incoming friend request Users (or null if bot account) + * @readonly + * @example + * // log names of the incoming friend requests that the client is aware of + * for(var user of client.incomingFriendRequests){ + * console.log(user.username); + * } + */ + }, { + key: "incomingFriendRequests", + get: function get() { + return this.internal.incoming_friend_requests; + } + + /** + * The outgoing friend requests that the Client is aware of. Only available after `ready` event has been emitted. + * @type {Cache} a Cache of outgoing friend request Users + * @readonly + * @example + * // log names of the outgoing friend requests that the client is aware of + * for(var user of client.outgoingFriendRequests){ + * console.log(user.username); + * } + */ + }, { + key: "outgoingFriendRequests", + get: function get() { + return this.internal.outgoing_friend_requests; + } + /** * The active voice connection of the Client, or null if not applicable. Only available after `ready` event has been emitted. * @type {VoiceConnection|null} the voice connection (if any). diff --git a/lib/Client/InternalClient.js b/lib/Client/InternalClient.js index d8bf118d6..b8b5cbeed 100644 --- a/lib/Client/InternalClient.js +++ b/lib/Client/InternalClient.js @@ -173,6 +173,9 @@ var InternalClient = (function () { // creates 4 caches with discriminators based on ID this.users = new _UtilCache2["default"](); + this.friends = new _UtilCache2["default"](); + this.outgoing_friend_requests = new _UtilCache2["default"](); + this.incoming_friend_requests = new _UtilCache2["default"](); this.channels = new _UtilCache2["default"](); this.servers = new _UtilCache2["default"](); this.private_channels = new _UtilCache2["default"](); @@ -1349,6 +1352,33 @@ var InternalClient = (function () { return this.setChannelNameAndTopic(chann, data.name, data.topic); }; + //def addFriend + + InternalClient.prototype.addFriend = function addFriend(user) { + if (this.user.bot) return Promise.reject(new Error("user is a bot, bot's do not have friends support")); + + var id; + if (user instanceof String || typeof user === "string") id = user;else if (user instanceof _StructuresUser2["default"]) { + user = this.resolver.resolveUser(user); + id = user.id; + } else { + if (user.username && user.discriminator) // add by username and discriminator (pass in an object) + return this.apiRequest("put", _Constants.Endpoints.FRIENDS, true, user);else return Promise.reject("invalid user"); + } + + return this.apiRequest("put", _Constants.Endpoints.FRIENDS + "/" + id, true, {}); + }; + + //def removeFriend + + InternalClient.prototype.removeFriend = function removeFriend(user) { + if (this.user.bot) return Promise.reject(new Error("user is a bot, bot's do not have friends support")); + + user = this.resolver.resolveUser(user); + + return this.apiRequest("delete", _Constants.Endpoints.FRIENDS + "/" + user.id, true); + }; + //def ack InternalClient.prototype.ack = function ack(msg) { @@ -1444,6 +1474,27 @@ var InternalClient = (function () { data.private_channels.forEach(function (pm) { self.private_channels.add(new _StructuresPMChannel2["default"](pm, client)); }); + if (!data.user.bot) { + // bots dont have friends + data.relationships.forEach(function (friend) { + if (friend.type === 1) { + // is a friend + self.friends.add(new _StructuresUser2["default"](friend.user, client)); + } else if (friend.type === 3) { + // incoming friend requests + self.incoming_friend_requests.add(new _StructuresUser2["default"](friend.user, client)); + } else if (friend.type === 4) { + // outgoing friend requests + self.outgoing_friend_requests.add(new _StructuresUser2["default"](friend.user, client)); + } else { + client.emit("warn", "unknown friend type " + friend.type); + } + }); + } else { + data.friends = null; + data.incoming_friend_requests = null; + data.outgoing_friend_requests = null; + } self.state = _ConnectionState2["default"].READY; client.emit("debug", "ready packet took " + (Date.now() - startTime) + "ms to process"); @@ -1982,6 +2033,53 @@ var InternalClient = (function () { client.emit("warn", "chunk update received but server not in cache"); } + break; + case _Constants.PacketType.FRIEND_ADD: + if (data.type === 1) { + // accepted/got accepted a friend request + var inUser = self.incoming_friend_requests.get("id", data.id); + if (inUser) { + // client accepted another user + self.incoming_friend_requests.remove(self.friends.add(new _StructuresUser2["default"](data.user, client))); + return; + } + + var outUser = self.outgoing_friend_requests.get("id", data.id); + if (outUser) { + // another user accepted the client + self.outgoing_friend_requests.remove(self.friends.add(new _StructuresUser2["default"](data.user, client))); + client.emit("friendRequestAccepted", outUser); + return; + } + } else if (data.type === 3) { + // client received friend request + client.emit("friendRequestReceived", self.incoming_friend_requests.add(new _StructuresUser2["default"](data.user, client))); + } else if (data.type === 4) { + // client sent friend request + self.outgoing_friend_requests.add(new _StructuresUser2["default"](data.user, client)); + } + break; + case _Constants.PacketType.FRIEND_REMOVE: + var user = self.friends.get("id", data.id); + if (user) { + self.friends.remove(user); + client.emit("friendRemoved", user); + return; + } + + user = self.incoming_friend_requests.get("id", data.id); + if (user) { + // they rejected friend request + client.emit("friendRequestRejected", self.outgoing_friend_requests.remove(user)); + return; + } + + user = self.outgoing_friend_requests.get("id", data.id); + if (user) { + // client cancelled friend request + self.incoming_friend_requests.remove(user); + return; + } break; default: client.emit("unknown", packet); diff --git a/lib/Constants.js b/lib/Constants.js index 12a4facde..a4d8caa8a 100644 --- a/lib/Constants.js +++ b/lib/Constants.js @@ -74,7 +74,10 @@ var Endpoints = { }, CHANNEL_MESSAGE: function CHANNEL_MESSAGE(channelID, messageID) { return Endpoints.CHANNEL_MESSAGES(channelID) + "/" + messageID; - } + }, + + // friends + FRIENDS: API + "/users/@me/relationships" }; exports.Endpoints = Endpoints; @@ -131,6 +134,8 @@ var PacketType = { SERVER_UPDATE: "GUILD_UPDATE", TYPING: "TYPING_START", USER_UPDATE: "USER_UPDATE", - VOICE_STATE_UPDATE: "VOICE_STATE_UPDATE" + VOICE_STATE_UPDATE: "VOICE_STATE_UPDATE", + FRIEND_ADD: "RELATIONSHIP_ADD", + FRIEND_REMOVE: "RELATIONSHIP_REMOVE" }; exports.PacketType = PacketType; diff --git a/src/Client/Client.js b/src/Client/Client.js index 78b4a2ae0..b47b86315 100644 --- a/src/Client/Client.js +++ b/src/Client/Client.js @@ -115,6 +115,48 @@ export default class Client extends EventEmitter { return this.internal.private_channels; } + /** + * The friends that the Client is aware of. Only available after `ready` event has been emitted. + * @type {Cache|null} a Cache of friend Users (or null if bot account) + * @readonly + * @example + * // log names of the friends that the client is aware of + * for(var user of client.friends){ + * console.log(user.username); + * } + */ + get friends() { + return this.internal.friends; + } + + /** + * The incoming friend requests that the Client is aware of. Only available after `ready` event has been emitted. + * @type {Cache|null} a Cache of incoming friend request Users (or null if bot account) + * @readonly + * @example + * // log names of the incoming friend requests that the client is aware of + * for(var user of client.incomingFriendRequests){ + * console.log(user.username); + * } + */ + get incomingFriendRequests() { + return this.internal.incoming_friend_requests; + } + + /** + * The outgoing friend requests that the Client is aware of. Only available after `ready` event has been emitted. + * @type {Cache} a Cache of outgoing friend request Users + * @readonly + * @example + * // log names of the outgoing friend requests that the client is aware of + * for(var user of client.outgoingFriendRequests){ + * console.log(user.username); + * } + */ + get outgoingFriendRequests() { + return this.internal.outgoing_friend_requests; + } + /** * The active voice connection of the Client, or null if not applicable. Only available after `ready` event has been emitted. * @type {VoiceConnection|null} the voice connection (if any). @@ -912,6 +954,18 @@ export default class Client extends EventEmitter { .then(dataCallback(callback), errorCallback(callback)); } + // def addFriend + addFriend(user, callback = (/*err, {}*/) => {}) { + return this.internal.addFriend(user) + .then(dataCallback(callback), errorCallback(callback)); + } + + // def removeFriend + removeFriend(user, callback = (/*err, {}*/) => {}) { + return this.internal.removeFriend(user) + .then(dataCallback(callback), errorCallback(callback)); + } + // def awaitResponse awaitResponse(msg, toSend = null, options = null, callback = (/*err, newMsg*/) => { }) { var ret; diff --git a/src/Client/InternalClient.js b/src/Client/InternalClient.js index e7e80b596..b6d385441 100644 --- a/src/Client/InternalClient.js +++ b/src/Client/InternalClient.js @@ -109,6 +109,9 @@ export default class InternalClient { // creates 4 caches with discriminators based on ID this.users = new Cache(); + this.friends = new Cache(); + this.outgoing_friend_requests = new Cache(); + this.incoming_friend_requests = new Cache(); this.channels = new Cache(); this.servers = new Cache(); this.private_channels = new Cache(); @@ -1013,7 +1016,7 @@ export default class InternalClient { //def updateDetails updateDetails(data) { - if (!this.user.bot && !(this.email || data.email)) + if (!this.user.bot && !(this.email || data.email)) throw new Error("Must provide email since a token was used to login"); var options = { @@ -1100,6 +1103,35 @@ export default class InternalClient { return this.setChannelNameAndTopic(chann, data.name, data.topic); } + //def addFriend + addFriend(user) { + if (this.user.bot) return Promise.reject(new Error("user is a bot, bot's do not have friends support")); + + var id; + if (user instanceof String || typeof user === "string") + id = user; + else if (user instanceof User) { + user = this.resolver.resolveUser(user); + id = user.id + } else { + if (user.username && user.discriminator) // add by username and discriminator (pass in an object) + return this.apiRequest("put", Endpoints.FRIENDS, true, user); + else + return Promise.reject("invalid user") + } + + return this.apiRequest("put", `${Endpoints.FRIENDS}/${id}`, true, {}); + } + + //def removeFriend + removeFriend(user) { + if (this.user.bot) return Promise.reject(new Error("user is a bot, bot's do not have friends support")); + + user = this.resolver.resolveUser(user); + + return this.apiRequest("delete", `${Endpoints.FRIENDS}/${user.id}`, true); + } + //def ack ack(msg) { msg = this.resolver.resolveMessage(msg); @@ -1190,6 +1222,23 @@ export default class InternalClient { data.private_channels.forEach(pm => { self.private_channels.add(new PMChannel(pm, client)); }); + if (!data.user.bot) { // bots dont have friends + data.relationships.forEach(friend => { + if (friend.type === 1) { // is a friend + self.friends.add(new User(friend.user, client)); + } else if (friend.type === 3) { // incoming friend requests + self.incoming_friend_requests.add(new User(friend.user, client)); + } else if (friend.type === 4) { // outgoing friend requests + self.outgoing_friend_requests.add(new User(friend.user, client)); + } else { + client.emit("warn", "unknown friend type " + friend.type); + } + }); + } else { + data.friends = null; + data.incoming_friend_requests = null; + data.outgoing_friend_requests = null; + } self.state = ConnectionState.READY; client.emit("debug", `ready packet took ${Date.now() - startTime}ms to process`); @@ -1667,6 +1716,50 @@ export default class InternalClient { client.emit("warn", "chunk update received but server not in cache"); } + break; + case PacketType.FRIEND_ADD: + if (data.type === 1) { // accepted/got accepted a friend request + var inUser = self.incoming_friend_requests.get("id", data.id); + if (inUser) { + // client accepted another user + self.incoming_friend_requests.remove(self.friends.add(new User(data.user, client))); + return; + } + + var outUser = self.outgoing_friend_requests.get("id", data.id); + if (outUser) { + // another user accepted the client + self.outgoing_friend_requests.remove(self.friends.add(new User(data.user, client))); + client.emit("friendRequestAccepted", outUser); + return; + } + } else if (data.type === 3) { + // client received friend request + client.emit("friendRequestReceived", self.incoming_friend_requests.add(new User(data.user, client))); + } else if (data.type === 4) { + // client sent friend request + self.outgoing_friend_requests.add(new User(data.user, client)); + } + break; + case PacketType.FRIEND_REMOVE: + var user = self.friends.get("id", data.id); + if (user) { + self.friends.remove(user); + client.emit("friendRemoved", user); + return; + } + + user = self.incoming_friend_requests.get("id", data.id); + if (user) { // they rejected friend request + client.emit("friendRequestRejected", self.outgoing_friend_requests.remove(user)); + return; + } + + user = self.outgoing_friend_requests.get("id", data.id); + if (user) {// client cancelled friend request + self.incoming_friend_requests.remove(user); + return; + } break; default: client.emit("unknown", packet); diff --git a/src/Constants.js b/src/Constants.js index c688594ba..f458ba5e2 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -32,7 +32,10 @@ export const Endpoints = { CHANNEL_INVITES: (channelID) => `${Endpoints.CHANNEL(channelID) }/invites`, CHANNEL_TYPING: (channelID) => `${Endpoints.CHANNEL(channelID) }/typing`, CHANNEL_PERMISSIONS: (channelID) => `${Endpoints.CHANNEL(channelID) }/permissions`, - CHANNEL_MESSAGE: (channelID, messageID) => `${Endpoints.CHANNEL_MESSAGES(channelID)}/${messageID}` + CHANNEL_MESSAGE: (channelID, messageID) => `${Endpoints.CHANNEL_MESSAGES(channelID)}/${messageID}`, + + // friends + FRIENDS: `${API}/users/@me/relationships` }; export const Permissions = { @@ -87,5 +90,7 @@ export const PacketType = { SERVER_UPDATE : "GUILD_UPDATE", TYPING : "TYPING_START", USER_UPDATE : "USER_UPDATE", - VOICE_STATE_UPDATE : "VOICE_STATE_UPDATE" + VOICE_STATE_UPDATE : "VOICE_STATE_UPDATE", + FRIEND_ADD : "RELATIONSHIP_ADD", + FRIEND_REMOVE : "RELATIONSHIP_REMOVE" };