diff --git a/src/client/Client.js b/src/client/Client.js index 368ef6c7c..82c229655 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -9,6 +9,7 @@ const ClientVoiceManager = require('./voice/ClientVoiceManager'); const WebSocketManager = require('./websocket/WebSocketManager'); const ActionsManager = require('./actions/ActionsManager'); const Collection = require('../util/Collection'); +const Presence = require('../structures/Presence'); /** * The starting point for making a Discord Bot. @@ -102,6 +103,13 @@ class Client extends EventEmitter { */ this.channels = new Collection(); + /** + * A Collection of presences for friends of the logged in user. + * This is only present for user accounts, not bot accounts! + * @type {Collection} + */ + this.presences = new Collection(); + /** * The authorization token for the logged in user/bot. * @type {?string} @@ -333,6 +341,14 @@ class Client extends EventEmitter { clearInterval(interval); this._intervals.delete(interval); } + + _setPresence(id, presence) { + if (this.presences.get(id)) { + this.presences.get(id).update(presence); + return; + } + this.presences.set(id, new Presence(presence)); + } } module.exports = Client; diff --git a/src/client/actions/GuildSync.js b/src/client/actions/GuildSync.js index 763acf788..d9b8dab02 100644 --- a/src/client/actions/GuildSync.js +++ b/src/client/actions/GuildSync.js @@ -8,11 +8,7 @@ class GuildSync extends Action { if (guild) { data.presences = data.presences || []; for (const presence of data.presences) { - const user = client.users.get(presence.user.id); - if (user) { - user.status = presence.status; - user.game = presence.game; - } + guild._setPresence(presence.user.id, presence); } data.members = data.members || []; diff --git a/src/client/websocket/packets/handlers/PresenceUpdate.js b/src/client/websocket/packets/handlers/PresenceUpdate.js index cfd7ee3a6..159b10753 100644 --- a/src/client/websocket/packets/handlers/PresenceUpdate.js +++ b/src/client/websocket/packets/handlers/PresenceUpdate.js @@ -19,9 +19,9 @@ class PresenceUpdateHandler extends AbstractHandler { } if (guild) { - const memberInGuild = guild.members.get(user.id); - if (!memberInGuild && data.status !== 'offline') { - const member = guild._addMember({ + let member = guild.members.get(user.id); + if (!member && data.status !== 'offline') { + member = guild._addMember({ user, roles: data.roles, deaf: false, @@ -29,26 +29,12 @@ class PresenceUpdateHandler extends AbstractHandler { }, false); client.emit(Constants.Events.GUILD_MEMBER_AVAILABLE, guild, member); } + guild._setPresence(user.id, data); } - data.user.username = data.user.username || user.username; - data.user.id = data.user.id || user.id; - data.user.discriminator = data.user.discriminator || user.discriminator; - data.user.status = data.status || user.status; - data.user.game = data.game; - - const same = data.user.username === user.username && - data.user.id === user.id && - data.user.discriminator === user.discriminator && - data.user.avatar === user.avatar && - data.user.status === user.status && - JSON.stringify(data.user.game) === JSON.stringify(user.game); - - if (!same) { - const oldUser = cloneObject(user); - user.patch(data.user); - client.emit(Constants.Events.PRESENCE_UPDATE, oldUser, user); - } + const oldUser = cloneObject(user); + user.patch(data.user); + client.emit(Constants.Events.PRESENCE_UPDATE, oldUser, user); } } diff --git a/src/client/websocket/packets/handlers/Ready.js b/src/client/websocket/packets/handlers/Ready.js index 32ffcb57f..7236148d3 100644 --- a/src/client/websocket/packets/handlers/Ready.js +++ b/src/client/websocket/packets/handlers/Ready.js @@ -16,6 +16,12 @@ class ReadyHandler extends AbstractHandler { for (const guild of data.guilds) client.dataManager.newGuild(guild); for (const privateDM of data.private_channels) client.dataManager.newChannel(privateDM); + data.presences = data.presences || []; + for (const presence of data.presences) { + client.dataManager.newUser(presence.user); + client._setPresence(presence.user.id, presence); + } + if (!client.user.bot) client.setInterval(client.syncGuilds.bind(client), 30000); client.once('ready', client.syncGuilds.bind(client)); diff --git a/src/structures/Guild.js b/src/structures/Guild.js index a14b554b6..fdd06de96 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -1,6 +1,7 @@ const User = require('./User'); const Role = require('./Role'); const Emoji = require('./Emoji'); +const Presence = require('./Presence'); const GuildMember = require('./GuildMember'); const Constants = require('../util/Constants'); const Collection = require('../util/Collection'); @@ -58,6 +59,14 @@ class Guild { } } + _setPresence(id, presence) { + if (this.presences.get(id)) { + this.presences.get(id).update(presence); + return; + } + this.presences.set(id, new Presence(presence)); + } + /** * Sets up the Guild * @param {*} data The raw data of the guild @@ -100,6 +109,12 @@ class Guild { */ this.large = data.large || this.large; + /** + * A collection of presences in this Guild + * @type {Collection} + */ + this.presences = new Collection(); + /** * An array of guild features. * @type {Object[]} @@ -170,11 +185,7 @@ class Guild { if (data.presences) { for (const presence of data.presences) { - const user = this.client.users.get(presence.user.id); - if (user) { - user.status = presence.status; - user.game = presence.game; - } + this._setPresence(presence.user.id, presence); } } diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index 55cdcc81e..bd5be285c 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -87,6 +87,15 @@ class GuildMember { this._joinDate = new Date(data.joined_at).getTime(); } + /** + * The presence of this Guild Member + * @type {Presence} + * @readonly + */ + get presence() { + return this.guild.presences.get(this.id); + } + /** * The date this member joined the guild * @type {Date} diff --git a/src/structures/Presence.js b/src/structures/Presence.js new file mode 100644 index 000000000..71dfc460e --- /dev/null +++ b/src/structures/Presence.js @@ -0,0 +1,89 @@ +/** + * Represents a Game that is part of a User's presence. + */ +class Game { + constructor(data) { + /** + * The name of the game being played + * @type {string} + */ + this.name = data.name; + /** + * The type of the game status + * @type {number} + */ + this.type = data.type; + /** + * If the game is being streamed, a link to the stream + * @type {string} + */ + this.url = data.url; + } + + /** + * Whether or not the game is being streamed + * @readonly + */ + get streaming() { + return this.type === 1; + } + + /** + * Whether this game is equal to another game + * @param {Game} other the other game to compare + * @returns {boolean} + */ + equals(other) { + return ( + this.name === other.name && + this.type === other.type && + this.url === other.url + ); + } +} + +class Presence { + constructor(data) { + /** + * The status of the presence: + * + * * **`online`** - user is online + * * **`offline`** - user is offline + * * **`idle`** - user is AFK + * @type {string} + */ + this.status = data.status || 'offline'; + if (data.game) { + /** + * The game that the user is playing, `null` if they aren't playing a game. + * @type {Game} + */ + this.game = new Game(data.game); + } else { + this.game = null; + } + } + + /** + * Whether this presence is equal to another + * @param {Presence} other the presence to compare + * @returns {boolean} + */ + equals(other) { + return ( + this.status === other.status && + this.game ? this.game.equals(other.game) : !other.game + ); + } + + update(data) { + this.status = data.status || this.status; + if (data.game) { + this.game = new Game(data.game); + } else { + this.game = null; + } + } +} + +module.exports = Presence; diff --git a/src/structures/User.js b/src/structures/User.js index 0f43d4ced..d54abc4fc 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -1,5 +1,6 @@ const TextBasedChannel = require('./interface/TextBasedChannel'); const Constants = require('../util/Constants'); +const Presence = require('./Presence'); /** * Represents a User on Discord. @@ -47,34 +48,26 @@ class User { * @type {boolean} */ this.bot = Boolean(data.bot); + } - /** - * The status of the user: - * - * * **`online`** - user is online - * * **`offline`** - user is offline - * * **`idle`** - user is AFK - * @type {string} - */ - this.status = data.status || this.status || 'offline'; - - /** - * Represents data about a Game - * @property {string} name the name of the game being played. - * @property {string} [url] the URL of the stream, if the game is being streamed. - * @property {number} [type] if being streamed, this is `1`. - * @typedef {object} Game - */ - - /** - * The game that the user is playing, `null` if they aren't playing a game. - * @type {Game} - */ - this.game = data.game; + /** + * The presence of this user + * @readonly + */ + get presence() { + if (this.client.presences.has(this.id)) { + return this.client.presences.get(this.id); + } + for (const guild of this.client.guilds.values()) { + if (guild.presences.has(this.id)) { + return guild.presences.get(this.id); + } + } + return new Presence(); } patch(data) { - for (const prop of ['id', 'username', 'discriminator', 'status', 'game', 'avatar', 'bot']) { + for (const prop of ['id', 'username', 'discriminator', 'avatar', 'bot']) { if (typeof data[prop] !== 'undefined') this[prop] = data[prop]; } } @@ -150,16 +143,6 @@ class User { this.avatar === user.avatar && this.bot === Boolean(user.bot); - if (equal) { - if (user.status) equal = this.status === user.status; - if (equal && user.game) { - equal = this.game && - this.game.name === user.game.name && - this.game.type === user.game.type && - this.game.url === user.game.url; - } - } - return equal; } diff --git a/test/random.js b/test/random.js index 7d0e011e0..1ac0db765 100644 --- a/test/random.js +++ b/test/random.js @@ -6,7 +6,7 @@ const fs = require('fs'); const client = new Discord.Client({ fetch_all_members: false, api_request_method: 'sequential' }); -const { email, password, token } = require('./auth.json'); +const { email, password, token, usertoken } = require('./auth.json'); client.login(token).then(atoken => console.log('logged in with token ' + atoken)).catch(console.log); @@ -18,28 +18,6 @@ client.on('channelCreate', channel => { console.log(`made ${channel.name}`); }); -client.on('guildMemberAdd', (g, m) => { - console.log(`${m.user.username} joined ${g.name}`); -}) - -let c = 0; - -client.on('channelUpdate', () => { - c++; console.log(c); -}); - -client.on('guildMemberUpdate', () => { - c++; console.log(c); -}); - -client.on('channelPinsUpdate', () => { - c++; console.log(c); -}); - -client.on('presenceUpdate', () => { - c++; console.log(c); -}); - client.on('debug', console.log); client.on('message', message => { diff --git a/test/shard.js b/test/shard.js index 8ae8633ba..ca05fe773 100644 --- a/test/shard.js +++ b/test/shard.js @@ -20,7 +20,12 @@ client.on('message', msg => { process.send(123); client.on('ready', () => { - console.log('Ready'); + console.log('Ready', client.options.shard_id); + if (client.options.shard_id === '0') + setTimeout(() => { + console.log('kek dying'); + client.destroy(); + }, 5000); }); client.login(token).catch(console.log); diff --git a/test/sharder.js b/test/sharder.js index 5a1340620..205c550a8 100644 --- a/test/sharder.js +++ b/test/sharder.js @@ -1,6 +1,6 @@ const Discord = require('../'); -const sharder = new Discord.ShardingManager(`${process.cwd()}/test/shard.js`); +const sharder = new Discord.ShardingManager(`${process.cwd()}/test/shard.js`, 5, false); sharder.on('launch', id => console.log(`launched ${id}`));