cloudstorm 0.1.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,309 +0,0 @@
1
- 'use strict';
2
- let Shard = require('./Shard');
3
-
4
- /**
5
- * @typedef ShardManager
6
- * @description Class used for managing shards for the user
7
- *
8
- * This class is automatically instantiated by the library and is documented for reference
9
- * @property {Client} client - client that created this shard manager
10
- * @property {Object} options - options of the [client](Client.html)
11
- * @property {Object} shards - Object with a map of shards, mapped by shard id
12
- * @property {Array} connectQueue - Array containing shards that are not connected yet or have to be reconnected
13
- * @property {Date} lastConnectionAttempt - unix timestamp of the last time a shard tried connecting to discord
14
- * @property {Number} connectQueueInterval - Time in milliseconds for the interval checking any shards that may need to be connected to discord
15
- */
16
- class ShardManager {
17
- /**
18
- * Create a new ShardManager
19
- * @param {Client} client
20
- * @private
21
- */
22
- constructor(client) {
23
- this.client = client;
24
- this.options = client.options;
25
- if (!this.options.connectQueueInterval) {
26
- this.options.connectQueueInterval = 1000 * 5;
27
- }
28
- this.shards = {};
29
- this.connectQueue = [];
30
- this.lastConnectionAttempt = null;
31
- this.connectQueueInterval = setInterval(() => {
32
- this._checkQueue();
33
- }, this.options.connectQueueInterval);
34
- }
35
-
36
- /**
37
- * Create the shard instances and add them to the connection queue
38
- * @protected
39
- */
40
- spawn() {
41
- for (let i = this.options.firstShardId; i < this.options.lastShardId + 1; i++) {
42
- /**
43
- * @event Client#debug
44
- * @type {String}
45
- * @description used for debugging of the internals of the library
46
- * @private
47
- */
48
- this.client.emit('debug', `Spawned shard ${i}`);
49
- this.shards[i] = new Shard(i, this.client);
50
- this.connectQueue.push({action: 'connect', shard: this.shards[i]});
51
- this._addListener(this.shards[i]);
52
- }
53
- }
54
-
55
- /**
56
- * Disconnect all shards
57
- * @protected
58
- */
59
- disconnect() {
60
- for (let shardKey in this.shards) {
61
- if (this.shards.hasOwnProperty(shardKey)) {
62
- let shard = this.shards[shardKey];
63
- shard.disconnect();
64
- }
65
- }
66
- }
67
-
68
- /**
69
- * Actually connect/re-identify a single shard by calling it's connect() or identify() method and reset the connection timer
70
- * @param {Object} data - Object with a shard and action key
71
- * @param {String} data.action - Action to execute, can either be `connect` or `identify`
72
- * @param {Shard} data.shard - shard that should connect to discord
73
- * @private
74
- */
75
- _connectShard({action, shard}) {
76
- /**
77
- * @event Client#debug
78
- * @type {String}
79
- * @description used for debugging of the internals of the library
80
- * @private
81
- */
82
- this.client.emit('debug', `${action === 'connect' ? 'Connecting' : 'Identifying'} Shard ${shard.id} Status: ${shard.connector.status} Ready: ${shard.ready}`);
83
- if (this.lastConnectionAttempt <= Date.now() - 6000) {
84
- switch (action) {
85
- case 'identify':
86
- this.lastConnectionAttempt = Date.now();
87
- this.client.emit('debug', `Identifying shard ${shard.id}`);
88
- shard.connector.identify(true);
89
- break;
90
- case 'connect':
91
- default:
92
- if (shard.connector.status !== 'connecting' && !shard.ready) {
93
- this.lastConnectionAttempt = Date.now();
94
- this.client.emit('debug', `Connecting shard ${shard.id}`);
95
- shard.connect();
96
- }
97
- break;
98
- }
99
- }
100
- }
101
-
102
- /**
103
- * Check if there are shards that are not connected yet and connect them if over 6 seconds have passed since the last attempt
104
- * @private
105
- */
106
- _checkQueue() {
107
- /**
108
- * @event Client#debug
109
- * @type {String}
110
- * @description used for debugging of the internals of the library
111
- * @private
112
- */
113
- this.client.emit('debug', `Checking queue Length: ${this.connectQueue.length} LastAttempt: ${this.lastConnectionAttempt} Current Time: ${Date.now()}`);
114
- if (this.connectQueue.length > 0 && this.lastConnectionAttempt <= Date.now() - 6000) {
115
- this._connectShard(...this.connectQueue.splice(0, 1));
116
- }
117
- }
118
-
119
- /**
120
- * Add event listeners to a shard to that the manager can act on received events
121
- * @param {Shard} shard - shard to add the event listeners to
122
- * @private
123
- */
124
- _addListener(shard) {
125
- shard.on('ready', (resume) => {
126
- this.shards[shard.id].ready = true;
127
- /**
128
- * @event Client#debug
129
- * @type {String}
130
- * @description used for debugging of the internals of the library
131
- * @private
132
- */
133
- this.client.emit('debug', `Shard ${shard.id} ${resume ? 'has resumed' : 'is ready'}`);
134
- /**
135
- * @event Client#shardReady
136
- * @type {Object}
137
- * @property {Number} id - id of the shard
138
- * @property {Boolean} ready - whether the shard turned ready or resumed
139
- * @description Emitted when a single shard resumes or turns ready
140
- */
141
- this.client.emit('shardReady', {id: shard.id, ready: !resume});
142
- this._checkReady();
143
- });
144
- shard.on('error', (error) => {
145
- /**
146
- * @event Client#error
147
- * @type {Error}
148
- * @description Emitted when an error occurs somewhere in the library
149
- */
150
- this.client.emit('error', error);
151
- });
152
-
153
- shard.on('disconnect', (code, reason, forceIdentify, gracefulClose) => {
154
- /**
155
- * @event Client#debug
156
- * @type {String}
157
- * @description used for debugging of the internals of the library
158
- * @private
159
- */
160
- this.client.emit('debug', `${shard.id} ws closed with code ${code} and reason: ${reason}`);
161
- if (code === 1000 && gracefulClose) {
162
- this._checkDisconnect();
163
- return;
164
- }
165
- shard.forceIdentify = forceIdentify;
166
- this.connectQueue.push({action: 'connect', shard});
167
- });
168
- shard.on('queueIdentify', (shardId) => {
169
- if (!this.shards[shardId]) {
170
- this.client.emit('debug', `Received a queueIdentify event for not existing shard ${shardId}`);
171
- return;
172
- }
173
- this.connectQueue.unshift({action: 'identify', shard: this.shards[shardId]});
174
- });
175
- }
176
-
177
- /**
178
- * Checks if all shards are ready
179
- * @private
180
- */
181
- _checkReady() {
182
- for (let shardId in this.shards) {
183
- if (this.shards.hasOwnProperty(shardId)) {
184
- if (!this.shards[shardId].ready) {
185
- return;
186
- }
187
- }
188
- }
189
- /**
190
- * @event Client#ready
191
- * @type {void}
192
- * @description Emitted when all shards turn ready
193
- * @example
194
- * //Connect bot to discord and get a log in the console once it's ready
195
- * let bot = new CloudStorm(token)
196
- * await bot.connect()
197
- * bot.on('ready', () => {
198
- * // The bot has connected to discord successfully and authenticated with the gateway
199
- * });
200
- */
201
- this.client.emit('ready');
202
- }
203
-
204
- /**
205
- * Checks if all shards are disconnected
206
- * @private
207
- */
208
- _checkDisconnect() {
209
- for (let shardId in this.shards) {
210
- if (this.shards.hasOwnProperty(shardId)) {
211
- if (this.shards[shardId].connector.status !== 'disconnected') {
212
- return;
213
- }
214
- }
215
- }
216
- /**
217
- * @event Client#disconnected
218
- * @type {void}
219
- * @description Emitted when all shards have disconnected successfully
220
- */
221
- this.client.emit('disconnected');
222
- }
223
-
224
- /**
225
- * Update the status of all currently connected shards
226
- * @param {Presence} data - payload to send
227
- * @protected
228
- */
229
- statusUpdate(data = {}) {
230
- let shardPromises = [];
231
- for (let shardKey in this.shards) {
232
- if (this.shards.hasOwnProperty(shardKey)) {
233
- let shard = this.shards[shardKey];
234
- if (shard.ready) {
235
- shardPromises.push(shard.statusUpdate(data));
236
- }
237
- }
238
- }
239
- return Promise.all(shardPromises);
240
- }
241
-
242
- /**
243
- * Update the status of a single connected shard
244
- * @param {Number} shardId - internal id of the shard
245
- * @param {Presence} data - payload to send
246
- * @protected
247
- */
248
- shardStatusUpdate(shardId, data = {}) {
249
- return new Promise((res, rej) => {
250
- let shard = this.shards[shardId];
251
- if (!shard) {
252
- rej(new Error(`Shard ${shardId} does not exist`));
253
- }
254
- if (!shard.ready) {
255
- shard.once('ready', () => {
256
- shard.statusUpdate(data).then(result => res(result)).catch(e => rej(e));
257
- });
258
- }
259
- shard.statusUpdate(data).then(result => res(result)).catch(e => rej(e));
260
- });
261
- }
262
-
263
- /**
264
- * Send a voice state update payload with a certain shard
265
- * @param {Number} shardId - id of the shard
266
- * @param {VoiceStateUpdate} data - payload to send
267
- * @returns {Promise.<void>}
268
- * @protected
269
- */
270
- voiceStateUpdate(shardId, data) {
271
- return new Promise((res, rej) => {
272
- let shard = this.shards[shardId];
273
- if (!shard) {
274
- rej(new Error(`Shard ${shardId} does not exist`));
275
- }
276
- if (!shard.ready) {
277
- shard.once('ready', () => {
278
- shard.voiceStateUpdate(data).then(result => res(result)).catch(e => rej(e));
279
- });
280
- }
281
- shard.voiceStateUpdate(data).then(result => res(result)).catch(e => rej(e));
282
- });
283
- }
284
-
285
- /**
286
- * Send a request guild members payload with a certain shard
287
- * @param {Number} shardId - id of the shard
288
- * @param {RequestGuildMembers} data - payload to send
289
- * @returns {Promise.<void>}
290
- * @protected
291
- */
292
- requestGuildMembers(shardId, data) {
293
- return new Promise((res, rej) => {
294
- let shard = this.shards[shardId];
295
- if (!shard) {
296
- rej(new Error(`Shard ${shardId} does not exist`));
297
- }
298
- if (!shard.ready) {
299
- shard.once('ready', () => {
300
- shard.requestGuildMembers(data).then(result => res(result)).catch(e => rej(e));
301
- });
302
- }
303
- shard.requestGuildMembers(data).then(result => res(result)).catch(e => rej(e));
304
- });
305
- }
306
-
307
- }
308
-
309
- module.exports = ShardManager;
@@ -1,403 +0,0 @@
1
- 'use strict';
2
- let EventEmitter;
3
- try {
4
- EventEmitter = require('eventemitter3');
5
- } catch (e) {
6
- EventEmitter = require('events').EventEmitter;
7
- }
8
- const BetterWs = require('../structures/BetterWs');
9
- const OP = require('../Constants').GATEWAY_OP_CODES;
10
-
11
- /**
12
- * @typedef DiscordConnector
13
- * @description Class used for acting based on received events
14
- *
15
- * This class is automatically instantiated by the library and is documented for reference
16
- * @property {String} id - id of the shard that created this class
17
- * @property {Client} client - Main client instance
18
- * @property {Object} options - options passed from the main client instance
19
- * @property {Boolean} reconnect - whether autoreconnect is enabled
20
- * @property {BetterWs} betterWs - Websocket class used for connecting to discord
21
- * @property {Object} heartbeatInterval - interval within which heartbeats should be sent to discord
22
- * @property {String[]} _trace - trace of servers used when connecting to discord
23
- * @property {Number} seq - sequence value used on RESUMES and heartbeats
24
- * @property {String} status - status of this connector
25
- * @property {String} sessionId - session id of the current session, used in RESUMES
26
- * @property {Boolean} forceIdentify - whether the connector should just IDENTIFY again and don't try to resume
27
- */
28
- class DiscordConnector extends EventEmitter {
29
- /**
30
- * Create a new Discord Connector
31
- * @param {String} id - id of the shard that created this class
32
- * @param {Client} client - Main client instance
33
- * @private
34
- */
35
- constructor(id, client) {
36
- super();
37
- this.id = id;
38
- this.client = client;
39
- this.options = client.options;
40
- this.reconnect = this.options.reconnect;
41
- this.betterWs = null;
42
- this.heartbeatInterval = null;
43
- this._trace = null;
44
- this.seq = 0;
45
- this.status = 'init';
46
- this.sessionId = null;
47
- this.forceIdentify = false;
48
- }
49
-
50
- /**
51
- * Connect to discord
52
- * @protected
53
- */
54
- connect() {
55
- if (!this.betterWs) {
56
- this.betterWs = new BetterWs(this.options.endpoint);
57
- } else {
58
- this.betterWs.removeAllListeners();
59
- this.betterWs.recreateWs(this.options.endpoint);
60
- }
61
- this.betterWs.on('ws_open', () => {
62
- this.status = 'connecting';
63
- });
64
- this.betterWs.on('ws_message', (msg) => {
65
- this.messageAction(msg);
66
- });
67
- this.betterWs.on('ws_close', (code, reason) => {
68
- this.client.emit('debug', `Websocket of shard ${this.id} closed with code ${code} and reason: ${reason}`);
69
- this.handleWsClose(code, reason);
70
- });
71
- this.betterWs.on('debug', event => {
72
- /**
73
- * @event Client#debug
74
- * @type {Object}
75
- * @description Debug event used for debugging the library
76
- * @private
77
- */
78
- this.client.emit('debug', event);
79
- });
80
- this.betterWs.on('debug_send', data => {
81
- /**
82
- * @event Client#rawSend
83
- * @type {Object}
84
- * @description Websocket payload which was sent to discord, this event is emitted on **every single** websocket message that was sent.
85
- */
86
- this.client.emit('rawSend', data);
87
- });
88
- }
89
-
90
- /**
91
- * Close the websocket connection and disconnect
92
- * @returns {Promise.<void>}
93
- * @protected
94
- */
95
- disconnect() {
96
- return this.betterWs.close(1000, 'Disconnect from User');
97
- }
98
-
99
- /**
100
- * Called with a parsed Websocket message to execute further actions
101
- * @param {Object} message - message that was received
102
- * @protected
103
- */
104
- messageAction(message) {
105
- /**
106
- * @event Client#rawReceive
107
- * @type {Object}
108
- * @description Websocket message received from discord, this event is emitted on **every single** websocket message you may receive.
109
- */
110
- this.client.emit('rawReceive', message);
111
- if (message.s) {
112
- if (message.s > this.seq + 1) {
113
- this.client.emit('debug', `Shard ${this.id}, invalid sequence: current: ${this.seq} message: ${message.s}`);
114
- this.seq = message.s;
115
- this.resume();
116
- }
117
- this.seq = message.s;
118
- }
119
- switch (message.op) {
120
- case OP.DISPATCH:
121
- this.handleDispatch(message);
122
- break;
123
- case OP.HELLO:
124
- this.heartbeat();
125
- this.heartbeatInterval = setInterval(() => {
126
- this.heartbeat(message.d.heartbeat_interval);
127
- }, message.d.heartbeat_interval - 5000);
128
- this._trace = message.d._trace;
129
- this.identify();
130
- this.client.emit('debug', `Shard ${this.id} received HELLO`);
131
- break;
132
- case OP.HEARTBEAT:
133
- this.heartbeat();
134
- break;
135
- case OP.HEARTBEAT_ACK:
136
- break;
137
- case OP.RECONNECT:
138
- this.reset();
139
- this.betterWs.close();
140
- break;
141
- case OP.INVALID_SESSION:
142
- if (message.d && this.sessionId) {
143
- this.resume();
144
- } else {
145
- this.seq = 0;
146
- this.sessionId = '';
147
- /**
148
- * @event DiscordConnector#queueIdentify
149
- * @type {Number}
150
- * @description Emitted when the connector received an op9 code
151
- * @private
152
- */
153
- this.emit('queueIdentify', this.id);
154
- }
155
- break;
156
- default:
157
- /**
158
- * @event DiscordConnector#event
159
- * @type {Object}
160
- * @description Forward the event
161
- * @private
162
- */
163
- this.emit('event', message);
164
- }
165
- }
166
-
167
- /**
168
- * Reset this connector
169
- * @protected
170
- */
171
- reset() {
172
- this.sessionId = null;
173
- this.seq = 0;
174
- this._trace = null;
175
- clearInterval(this.heartbeatInterval);
176
- this.heartbeatInterval = null;
177
- }
178
-
179
- /**
180
- * Send a identify payload to the gateway
181
- * @param {Boolean} force - Whether CloudStorm should send an IDENTIFY even if there's a session that could be resumed
182
- * @returns {Promise.<void>}
183
- * @protected
184
- */
185
- identify(force) {
186
- if (this.sessionId && !this.forceIdentify && !force) {
187
- return this.resume();
188
- }
189
- let data = {
190
- op: OP.IDENTIFY, d: {
191
- token: this.options.token,
192
- properties: {
193
- os: process.platform,
194
- browser: 'CloudStorm',
195
- device: 'CloudStorm'
196
- },
197
- large_threshold: this.options.largeGuildThreshold,
198
- shard: [this.id, this.options.shardAmount],
199
- presence: this.options.initialPresence ? this._checkPresenceData(this.options.initialPresence) : null
200
- }
201
- };
202
- this.forceIdentify = false;
203
- return this.betterWs.sendMessage(data);
204
- }
205
-
206
- /**
207
- * Send a resume payload to the gateway
208
- * @returns {Promise.<void>}
209
- * @protected
210
- */
211
- resume() {
212
- return this.betterWs.sendMessage({
213
- op: OP.RESUME,
214
- d: {seq: this.seq, token: this.options.token, session_id: this.sessionId}
215
- });
216
- }
217
-
218
- /**
219
- * Send a heartbeat to discord
220
- * @protected
221
- */
222
- heartbeat() {
223
- this.betterWs.sendMessage({op: OP.HEARTBEAT, d: this.seq});
224
- }
225
-
226
- /**
227
- * Handle dispatch events
228
- * @param {Object} message - message received from the websocket
229
- * @protected
230
- */
231
- handleDispatch(message) {
232
- switch (message.t) {
233
- case 'READY':
234
- case 'RESUMED':
235
- if (message.t === 'READY') {
236
- this.sessionId = message.d.session_id;
237
- }
238
- this.status = 'ready';
239
- this._trace = message.d._trace;
240
- /**
241
- * @event DiscordConnector#ready
242
- * @type {void}
243
- * @description Emitted once the connector is ready (again)
244
- * @private
245
- */
246
- this.emit('ready', message.t === 'RESUMED');
247
- /**
248
- * @event DiscordConnector#event
249
- * @type {Object}
250
- * @description Emitted once an event was received from discord
251
- * @private
252
- */
253
- this.emit('event', message);
254
- break;
255
- default:
256
- /**
257
- * @event DiscordConnector#event
258
- * @type {Object}
259
- * @description Emitted once an event was received from discord
260
- * @private
261
- */
262
- this.emit('event', message);
263
- }
264
- }
265
-
266
- /**
267
- * Handle a close from the underlying websocket
268
- * @param {Number} code - websocket close code
269
- * @param {String} reason - close reason if any
270
- * @protected
271
- */
272
- handleWsClose(code, reason) {
273
- let forceIdentify = false;
274
- let gracefulClose = false;
275
- this.status = 'disconnected';
276
- if (code === 4004) {
277
- /**
278
- * @event DiscordConnector#error
279
- * @type {String}
280
- * @description Emitted when the token was invalid
281
- * @private
282
- */
283
- this.emit('error', 'Tried to connect with an invalid token');
284
- return;
285
- }
286
- if (code === 4010) {
287
- /**
288
- * @event DiscordConnector#error
289
- * @type {String}
290
- * @description Emitted when the user tried to connect with bad sharding data
291
- * @private
292
- */
293
- this.emit('error', 'Invalid sharding data, check your client options');
294
- return;
295
- }
296
- if (code === 4011) {
297
- /**
298
- * @event DiscordConnector#error
299
- * @type {String}
300
- * @description Emitted when the shard would be on over 2500 guilds
301
- * @private
302
- */
303
- this.emit('error', 'Shard would be on over 2500 guilds. Add more shards');
304
- return;
305
- }
306
- // force identify if the session is marked as invalid
307
- if (code === 4009) {
308
- forceIdentify = true;
309
- }
310
- // don't try to reconnect when true
311
- if (code === 1000 && reason === 'Disconnect from User') {
312
- gracefulClose = true;
313
- }
314
- clearInterval(this.heartbeatInterval);
315
- this.betterWs.removeAllListeners();
316
- /**
317
- * @event DiscordConnector#disconnect
318
- * @type {Object}
319
- * @property {Number} code - websocket disconnect code
320
- * @private
321
- */
322
- this.emit('disconnect', code, reason, forceIdentify, gracefulClose);
323
- }
324
-
325
- /**
326
- * Send a status update payload to discord
327
- * @param {Presence} data - presence data to send
328
- * @protected
329
- */
330
- statusUpdate(data = {}) {
331
- return this.betterWs.sendMessage({op: OP.STATUS_UPDATE, d: this._checkPresenceData(data)});
332
- }
333
-
334
- /**
335
- * Send a voice state update payload to discord
336
- * @param {VoiceStateUpdate} data - voice state update data to send
337
- * @returns {Promise.<void>}
338
- * @protected
339
- */
340
- voiceStateUpdate(data) {
341
- if (!data) {
342
- return Promise.resolve();
343
- }
344
- return this.betterWs.sendMessage({op: OP.VOICE_STATE_UPDATE, d: this._checkVoiceStateUpdateData(data)});
345
- }
346
-
347
- /**
348
- * Send a request guild members payload to discord
349
- * @param {RequestGuildMembers} data - data to send
350
- * @returns {Promise.<void>}
351
- * @protected
352
- */
353
- requestGuildMembers(data = {}) {
354
- return this.betterWs.sendMessage({op: OP.REQUEST_GUILD_MEMBERS, d: this._checkRequestGuildMembersData(data)});
355
- }
356
-
357
- /**
358
- * Checks the presence data and fills in missing elements
359
- * @param {Object} data - data to send
360
- * @returns {Object} data after it's fixed/checked
361
- * @private
362
- */
363
- _checkPresenceData(data) {
364
- data.status = data.status || 'online';
365
- data.game = data.game || null;
366
- if (data.game && !data.game.type) {
367
- data.game.type = data.game.url ? 1 : 0;
368
- }
369
- if (data.game && !data.game.name) {
370
- data.game = null;
371
- }
372
- data.afk = data.afk || false;
373
- data.since = data.since || false;
374
- return data;
375
- }
376
-
377
- /**
378
- * Checks the voice state update data and fills in missing elements
379
- * @param {Object} data - data to send
380
- * @returns {Object} data after it's fixed/checked
381
- * @private
382
- */
383
- _checkVoiceStateUpdateData(data) {
384
- data.channel_id = data.channel_id || null;
385
- data.self_mute = data.self_mute || false;
386
- data.self_deaf = data.self_deaf || false;
387
- return data;
388
- }
389
-
390
- /**
391
- * Checks the request guild members data and fills in missing elements
392
- * @param {Object} data - data to send
393
- * @returns {Object} data after it's fixed/checked
394
- * @private
395
- */
396
- _checkRequestGuildMembersData(data) {
397
- data.query = data.query || '';
398
- data.limit = data.limit || 0;
399
- return data;
400
- }
401
- }
402
-
403
- module.exports = DiscordConnector;