cloudstorm 0.1.4 → 0.3.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,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;
@@ -1,234 +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 zlib = require('zlib-sync');
9
- let Erlpack;
10
- try {
11
- Erlpack = require('erlpack');
12
- } catch (e) {// eslint-disable-next-line no-empty
13
- }
14
- const GATEWAY_OP_CODES = require('../Constants').GATEWAY_OP_CODES;
15
- let WebSocket = require('ws');
16
- let RatelimitBucket = require('./RatelimitBucket');
17
-
18
- /**
19
- * @typedef BetterWs
20
- * @description Helper Class for simplifying the websocket connection to discord
21
- * @property {WebSocket} ws - the raw websocket connection
22
- * @property {RatelimitBucket} wsBucket - ratelimit bucket for the general websocket connection
23
- * @property {RatelimitBucket} statusBucket - ratelimit bucket for the 5/60s status update ratelimit
24
- * @property {zlib} zlibInflate - shared zlibInflate context for inflating zlib compressed messages received from discord
25
- * @private
26
- */
27
- class BetterWs extends EventEmitter {
28
- /**
29
- * Create a new BetterWs instance
30
- * @param {String} address
31
- * @param {Object} options
32
- * @private
33
- */
34
- constructor(address, options = {}) {
35
- super();
36
- this.ws = new WebSocket(address, options);
37
- this.bindWs(this.ws);
38
- this.wsBucket = new RatelimitBucket(120, 60000);
39
- this.statusBucket = new RatelimitBucket(5, 60000);
40
- this.zlibInflate = new zlib.Inflate({
41
- chunkSize: 65535,
42
- flush: zlib.Z_SYNC_FLUSH,
43
- });
44
- }
45
-
46
- /**
47
- * Get the raw websocket connection currently used
48
- * @returns {WebSocket}
49
- * @protected
50
- */
51
- get rawWs() {
52
- return this.ws;
53
- }
54
-
55
- /**
56
- * Add eventlisteners to a passed websocket connection
57
- * @param {WebSocket} ws - websocket
58
- * @protected
59
- */
60
- bindWs(ws) {
61
- ws.on('message', (msg) => {
62
- this.onMessage(msg);
63
- });
64
- ws.on('close', (code, reason) => this.onClose(code, reason));
65
- ws.on('error', (err) => {
66
- /**
67
- * @event BetterWs#error
68
- * @type {Error}
69
- * @description Emitted upon errors from the underlying websocket
70
- * @private
71
- */
72
- this.emit('error', err);
73
- });
74
- ws.on('open', () => this.onOpen());
75
- }
76
-
77
- /**
78
- * Create a new Websocket Connection if the old one was closed/destroyed
79
- * @param {String} address - address to connect to
80
- * @param {Object} options - options used by the websocket connection
81
- * @protected
82
- */
83
- recreateWs(address, options = {}) {
84
- this.ws.removeAllListeners();
85
- this.zlibInflate = new zlib.Inflate({
86
- chunkSize: 65535,
87
- flush: zlib.Z_SYNC_FLUSH,
88
- });
89
- this.ws = new WebSocket(address, options);
90
- this.options = options;
91
- this.wsBucket.dropQueue();
92
- this.wsBucket = new RatelimitBucket(120, 60000);
93
- this.statusBucket = new RatelimitBucket(5, 60000);
94
- this.bindWs(this.ws);
95
- }
96
-
97
- /**
98
- * Called upon opening of the websocket connection
99
- * @protected
100
- */
101
- onOpen() {
102
- /**
103
- * @event BetterWs#ws_open
104
- * @type {void}
105
- * @description Emitted once the underlying websocket connection has opened
106
- * @private
107
- */
108
- this.emit('ws_open');
109
- }
110
-
111
- /**
112
- * Called once a websocket message is received,
113
- * uncompresses the message using zlib and parses it via Erlpack or JSON.parse
114
- * @param {Object|Buffer|String} message - message received by websocket
115
- * @protected
116
- */
117
- onMessage(message) {
118
- try {
119
- const length = message.length;
120
- const flush = length >= 4 &&
121
- message[length - 4] === 0x00 &&
122
- message[length - 3] === 0x00 &&
123
- message[length - 2] === 0xFF &&
124
- message[length - 1] === 0xFF;
125
- this.zlibInflate.push(message, flush && zlib.Z_SYNC_FLUSH);
126
- if (!flush) return;
127
- if (Erlpack) {
128
- message = Erlpack.unpack(this.zlibInflate.result);
129
- } else {
130
- message = JSON.parse(this.zlibInflate.result);
131
- }
132
- } catch (e) {
133
- /**
134
- * @event BetterWs#error
135
- * @type {String}
136
- * @description Emitted upon parse errors of messages
137
- * @private
138
- */
139
- this.emit('error', `Message: ${message} was not parseable`);
140
- return;
141
- }
142
- /**
143
- * @event BetterWs#ws_message
144
- * @type {Object}
145
- * @description Emitted upon successful parsing of a message with the parsed Message
146
- * @private
147
- */
148
- this.emit('ws_message', message);
149
- }
150
-
151
- /**
152
- * Called when the websocket connection closes for some reason
153
- * @param {Number} code - websocket close code
154
- * @param {String} reason - reason of the close if any
155
- * @protected
156
- */
157
- onClose(code, reason) {
158
- /**
159
- * @event BetterWs#ws_close
160
- * @type {void}
161
- * @param {Number} code - websocket close code
162
- * @param {String} reason - websocket close reason
163
- * @private
164
- */
165
- this.emit('ws_close', code, reason);
166
- }
167
-
168
- /**
169
- * Send a message to the discord gateway
170
- * @param {Object} data - data to send
171
- * @returns {Promise.<void>}
172
- * @protected
173
- */
174
- sendMessage(data) {
175
- /**
176
- * @event BetterWs#debug_send
177
- * @type {object}
178
- * @description Used for debugging the messages sent to discord's gateway
179
- * @private
180
- */
181
- this.emit('debug_send', data);
182
- return new Promise((res, rej) => {
183
- let status = data.op === GATEWAY_OP_CODES.STATUS_UPDATE;
184
- try {
185
- if (Erlpack) {
186
- data = Erlpack.pack(data);
187
- } else {
188
- data = JSON.stringify(data);
189
- }
190
- } catch (e) {
191
- return rej(e);
192
- }
193
- let sendMsg = () => {
194
- // The promise from wsBucket is ignored, since the method passed to it does not return a promise
195
- this.wsBucket.queue(() => {
196
- this.ws.send(data, {}, (e) => {
197
- if (e) {
198
- return rej(e);
199
- }
200
- res();
201
- });
202
- });
203
- };
204
- if (status) {
205
- // same here
206
- this.statusBucket.queue(sendMsg);
207
- } else {
208
- sendMsg();
209
- }
210
- });
211
- }
212
-
213
- /**
214
- * Close the current connection
215
- * @param {Number} code=1000 - websocket close code to use
216
- * @param {String} reason - reason of the disconnect
217
- * @returns {Promise.<void>}
218
- * @protected
219
- */
220
- close(code = 1000, reason = '') {
221
- return new Promise((res, rej) => {
222
- this.ws.close(code, reason);
223
- this.ws.once('close', () => {
224
- return res();
225
- });
226
- setTimeout(() => {
227
- return rej('Websocket not closed within 5 seconds');
228
- }, 5 * 1000);
229
- });
230
-
231
- }
232
- }
233
-
234
- module.exports = BetterWs;