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}`));