mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
feat: Internal sharding (#2902)
* internal sharding * ready event * the square deal * the new deal * the second new deal * add actual documentation * the new freedom * the great society * federal intervention * some of requested changes * i ran out of things to call these * destroy this * fix: Client#uptime went missing * fix(Client): destroy the client on login failure This may happen duo invalid sharding config / invalid token / user requested destroy * fix(Client): reject login promise when the client is destroyed before ready * fix(WebSocketManager): remove redundancy in destroy method (#2491) * typo(ErrorMessages): duo -> duo to * typo(ErrorMessages): duo -> due * fix: docs and options * docs(WebSocketManager): WebSockethard -> WebSocketShard (#2502) * fix(ClientUser): lazily load to account for extended user structure (#2501) * docs(WebSocketShard): document class to make it visible in documentation (#2504) * fix: WebSocketShard#reconnect * fix: presenceUpdate & userUpdate * presenceUpdate wasn't really being handled at all * userUpdate handled incorrectly because as of v7 in the Discord API, it comes inside presenceUpdate * re-add raw event * member is now part of message create payload * feat: Add functionality to support multiple servers with different shards (#2395) * Added functionallity to spawn multiple sharding managers due to adding start and end shards * Small fixes and limiting shard amount to max recommended * Forgot a check in spawn() * Fixed indentation * Removed optiosn object documentation for totalShards * More fixes and a check that the startShard + amount doesnt go over the recommended shard amount * fix getting max recommended * Removed async from constructor (my fault) * Changed start and end shard to a shardList or "auto" + fixed some brainfarts with isNaN * Changed the loop and totalShard count calculation * shards are actually 0 based * Fixed a problem with the gateway and handled some range errors and type errors * Changed Number.isNan to isNaN and changed a few Integer checks to use Number.isInteger * Added check if shardList contains smth greater than totalShards; made spawn use totalShards again; shardList will be ignored and rebuild if totalShards is 'auto'; fixed docs * ShardingManager#spawn now uses a for..of loop; fixed the if statement inside the new for..of loop to still work as intended; made the totalShards be set to a new amount if smth manual is put into ShardingManager#spawn just like before; Fixed some spelling * internal sharding * ready event * the square deal * the new deal * the second new deal * add actual documentation * the new freedom * the great society * federal intervention * some of requested changes * i ran out of things to call these * destroy this * fix: Client#uptime went missing * fix(Client): destroy the client on login failure This may happen duo invalid sharding config / invalid token / user requested destroy * fix(Client): reject login promise when the client is destroyed before ready * fix(WebSocketManager): remove redundancy in destroy method (#2491) * typo(ErrorMessages): duo -> duo to * typo(ErrorMessages): duo -> due * fix: docs and options * docs(WebSocketManager): WebSockethard -> WebSocketShard (#2502) * fix(ClientUser): lazily load to account for extended user structure (#2501) * docs(WebSocketShard): document class to make it visible in documentation (#2504) * fix: WebSocketShard#reconnect * fix: presenceUpdate & userUpdate * presenceUpdate wasn't really being handled at all * userUpdate handled incorrectly because as of v7 in the Discord API, it comes inside presenceUpdate * Internal Sharding adaptation Adapted to internal sharding Fixed a bug where non ready invalidated sessions wouldnt respawn * Fixed shardCount not retrieving * Fixing style removed unnecessary parenthesis * Fixing and rebasing lets hope i didnt dun hecklered it * Fixing my own retardation * Thanks git rebase * fix: assigning member in message create payload * fix: resumes * fix: IS wont give up reconnecting now * docs: add missing docs mostly * fix: found lost methods * fix: WebSocketManager#broadcast check if shard exists * fix: ShardClientUtil#id returning undefined * feat: handle new session rate limits (#2796) * feat: handle new session rate limits * i have no idea what i was doing last night * fix if statement weirdness * fix: re-add presence parsing from ClientOptions (#2893) * resolve conflicts * typings: missing typings * re-add missing linter rule * fix: replacing ClientUser wrongly * address unecessary performance waste * docs: missing disconnect event * fix(typings): Fix 2 issues with typings (#2909) * (Typings) Update typings to reflect current ClientOptions * fix(Typings) fixes a bug with Websockets and DOM Types * fix travis * feat: allow setting presence per shard * add WebSocketManager#shardX events * adjust typings, docs and performance issues * readjust shard events, now provide shardId parameter instead * fix: ready event should check shardCount, not actualShardCount * fix: re-add replayed parameter of Client#resume * fix(Sharding): fixes several things in Internal Sharding (#2914) * fix(Sharding) fixes several things in Internal Sharding * add default value for shards property * better implement checking for shards array * fix travis & some casing * split shard count into 2 words * update to latest Internal Sharding, fix requested changes * make sure totalShardCount is a number * fix comment * fix small typo * dynamically set totalShardCount if either shards or shardCount is provided * consistency: rename shardID to shardId * remove Client#shardIds * fix: typo in GuildIntegrationsUpdate handler * fix: incorrect packet data being passed in some events (#2919) * fix: edgecase of ShardingManager and totalShardCount (#2918) * fix: Client#userUpdate being passed wrong parameter and fix a potential edgecase of returning null in ClientUser#edit from this event * fix consistency and typings issues * consistency: shardId instances renamed to shardID * typings: fix typings regarding WebSocket * style(.eslintrc): remove additional whitespace * fix(Client): remove ondisconnect handler on timeout * docs(BaseClient): fix typo of Immediate * nitpick: typings, private fields and methods * typo: improve grammar a bit * fix: error assigning client in WebSocketManager * typo: actually spell milliseconds properly
This commit is contained in:
@@ -39,6 +39,7 @@
|
||||
"node-fetch": "^2.1.2",
|
||||
"pako": "^1.0.0",
|
||||
"prism-media": "amishshah/prism-media",
|
||||
"setimmediate": "^1.0.5",
|
||||
"tweetnacl": "^1.0.0",
|
||||
"ws": "^6.0.0"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
require('setimmediate');
|
||||
const EventEmitter = require('events');
|
||||
const RESTManager = require('../rest/RESTManager');
|
||||
const Util = require('../util/Util');
|
||||
@@ -25,6 +26,13 @@ class BaseClient extends EventEmitter {
|
||||
*/
|
||||
this._intervals = new Set();
|
||||
|
||||
/**
|
||||
* Intervals set by {@link BaseClient#setImmediate} that are still active
|
||||
* @type {Set<Immediate>}
|
||||
* @private
|
||||
*/
|
||||
this._immediates = new Set();
|
||||
|
||||
/**
|
||||
* The options the client was instantiated with
|
||||
* @type {ClientOptions}
|
||||
@@ -53,10 +61,12 @@ class BaseClient extends EventEmitter {
|
||||
* Destroys all assets used by the base client.
|
||||
*/
|
||||
destroy() {
|
||||
for (const t of this._timeouts) clearTimeout(t);
|
||||
for (const i of this._intervals) clearInterval(i);
|
||||
for (const t of this._timeouts) this.clearTimeout(t);
|
||||
for (const i of this._intervals) this.clearInterval(i);
|
||||
for (const i of this._immediates) this.clearImmediate(i);
|
||||
this._timeouts.clear();
|
||||
this._intervals.clear();
|
||||
this._immediates.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,6 +116,27 @@ class BaseClient extends EventEmitter {
|
||||
this._intervals.delete(interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an immediate that will be automatically cancelled if the client is destroyed.
|
||||
* @param {Function} fn Function to execute
|
||||
* @param {...*} args Arguments for the function
|
||||
* @returns {Immediate}
|
||||
*/
|
||||
setImmediate(fn, ...args) {
|
||||
const immediate = setImmediate(fn, ...args);
|
||||
this._immediates.add(immediate);
|
||||
return immediate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears an immediate.
|
||||
* @param {Immediate} immediate Immediate to cancel
|
||||
*/
|
||||
clearImmediate(immediate) {
|
||||
clearImmediate(immediate);
|
||||
this._immediates.delete(immediate);
|
||||
}
|
||||
|
||||
toJSON(...props) {
|
||||
return Util.flatten(this, { domain: false }, ...props);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const BaseClient = require('./BaseClient');
|
||||
const Permissions = require('../util/Permissions');
|
||||
const ClientManager = require('./ClientManager');
|
||||
const ClientVoiceManager = require('./voice/ClientVoiceManager');
|
||||
const WebSocketManager = require('./websocket/WebSocketManager');
|
||||
const ActionsManager = require('./actions/ActionsManager');
|
||||
@@ -15,7 +14,8 @@ const UserStore = require('../stores/UserStore');
|
||||
const ChannelStore = require('../stores/ChannelStore');
|
||||
const GuildStore = require('../stores/GuildStore');
|
||||
const GuildEmojiStore = require('../stores/GuildEmojiStore');
|
||||
const { Events, browser } = require('../util/Constants');
|
||||
const { Events, WSCodes, browser, DefaultOptions } = require('../util/Constants');
|
||||
const { delayFor } = require('../util/Util');
|
||||
const DataResolver = require('../util/DataResolver');
|
||||
const Structures = require('../util/Structures');
|
||||
const { Error, TypeError, RangeError } = require('../errors');
|
||||
@@ -31,45 +31,34 @@ class Client extends BaseClient {
|
||||
constructor(options = {}) {
|
||||
super(Object.assign({ _tokenType: 'Bot' }, options));
|
||||
|
||||
// Figure out the shard details
|
||||
if (!browser && process.env.SHARDING_MANAGER) {
|
||||
// Try loading workerData if it's present
|
||||
let workerData;
|
||||
try {
|
||||
workerData = require('worker_threads').workerData;
|
||||
} catch (err) {
|
||||
// Do nothing
|
||||
// Obtain shard details from environment or if present, worker threads
|
||||
let data = process.env;
|
||||
try {
|
||||
// Test if worker threads module is present and used
|
||||
data = require('worker_threads').workerData || data;
|
||||
} catch (_) {
|
||||
// Do nothing
|
||||
}
|
||||
if (this.options.shards === DefaultOptions.shards) {
|
||||
if ('SHARDS' in data) {
|
||||
this.options.shards = JSON.parse(data.SHARDS);
|
||||
}
|
||||
|
||||
if (!this.options.shardId) {
|
||||
if (workerData && 'SHARD_ID' in workerData) {
|
||||
this.options.shardId = workerData.SHARD_ID;
|
||||
} else if ('SHARD_ID' in process.env) {
|
||||
this.options.shardId = Number(process.env.SHARD_ID);
|
||||
}
|
||||
}
|
||||
if (!this.options.shardCount) {
|
||||
if (workerData && 'SHARD_COUNT' in workerData) {
|
||||
this.options.shardCount = workerData.SHARD_COUNT;
|
||||
} else if ('SHARD_COUNT' in process.env) {
|
||||
this.options.shardCount = Number(process.env.SHARD_COUNT);
|
||||
}
|
||||
}
|
||||
if (this.options.totalShardCount === DefaultOptions.totalShardCount) {
|
||||
if ('TOTAL_SHARD_COUNT' in data) {
|
||||
this.options.totalShardCount = Number(data.TOTAL_SHARD_COUNT);
|
||||
} else if (Array.isArray(this.options.shards)) {
|
||||
this.options.totalShardCount = this.options.shards.length;
|
||||
} else {
|
||||
this.options.totalShardCount = this.options.shardCount;
|
||||
}
|
||||
}
|
||||
|
||||
this._validateOptions();
|
||||
|
||||
/**
|
||||
* The manager of the client
|
||||
* @type {ClientManager}
|
||||
* @private
|
||||
*/
|
||||
this.manager = new ClientManager(this);
|
||||
|
||||
/**
|
||||
* The WebSocket manager of the client
|
||||
* @type {WebSocketManager}
|
||||
* @private
|
||||
*/
|
||||
this.ws = new WebSocketManager(this);
|
||||
|
||||
@@ -155,54 +144,11 @@ class Client extends BaseClient {
|
||||
*/
|
||||
this.broadcasts = [];
|
||||
|
||||
/**
|
||||
* Previous heartbeat pings of the websocket (most recent first, limited to three elements)
|
||||
* @type {number[]}
|
||||
*/
|
||||
this.pings = [];
|
||||
|
||||
if (this.options.messageSweepInterval > 0) {
|
||||
this.setInterval(this.sweepMessages.bind(this), this.options.messageSweepInterval * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp of the latest ping's start time
|
||||
* @type {number}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
get _pingTimestamp() {
|
||||
return this.ws.connection ? this.ws.connection.lastPingTimestamp : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current status of the client's connection to Discord
|
||||
* @type {?Status}
|
||||
* @readonly
|
||||
*/
|
||||
get status() {
|
||||
return this.ws.connection ? this.ws.connection.status : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* How long it has been since the client last entered the `READY` state in milliseconds
|
||||
* @type {?number}
|
||||
* @readonly
|
||||
*/
|
||||
get uptime() {
|
||||
return this.readyAt ? Date.now() - this.readyAt : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Average heartbeat ping of the websocket, obtained by averaging the {@link Client#pings} property
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get ping() {
|
||||
return this.pings.reduce((prev, p) => prev + p, 0) / this.pings.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* All active voice connections that have been established, mapped by guild ID
|
||||
* @type {Collection<Snowflake, VoiceConnection>}
|
||||
@@ -235,6 +181,15 @@ class Client extends BaseClient {
|
||||
return this.readyAt ? this.readyAt.getTime() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* How long it has been since the client last entered the `READY` state in milliseconds
|
||||
* @type {?number}
|
||||
* @readonly
|
||||
*/
|
||||
get uptime() {
|
||||
return this.readyAt ? Date.now() - this.readyAt : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a voice broadcast.
|
||||
* @returns {VoiceBroadcast}
|
||||
@@ -252,15 +207,54 @@ class Client extends BaseClient {
|
||||
* @example
|
||||
* client.login('my token');
|
||||
*/
|
||||
login(token = this.token) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!token || typeof token !== 'string') throw new Error('TOKEN_INVALID');
|
||||
token = token.replace(/^Bot\s*/i, '');
|
||||
this.manager.connectToWebSocket(token, resolve, reject);
|
||||
}).catch(e => {
|
||||
this.destroy();
|
||||
return Promise.reject(e);
|
||||
async login(token = this.token) {
|
||||
if (!token || typeof token !== 'string') throw new Error('TOKEN_INVALID');
|
||||
this.token = token = token.replace(/^(Bot|Bearer)\s*/i, '');
|
||||
this.emit(Events.DEBUG, `Authenticating using token ${token}`);
|
||||
let endpoint = this.api.gateway;
|
||||
if (this.options.shardCount === 'auto') endpoint = endpoint.bot;
|
||||
const res = await endpoint.get();
|
||||
if (this.options.presence) {
|
||||
this.options.ws.presence = await this.presence._parse(this.options.presence);
|
||||
}
|
||||
if (res.session_start_limit && res.session_start_limit.remaining === 0) {
|
||||
const { session_start_limit: { reset_after } } = res;
|
||||
this.emit(Events.DEBUG, `Exceeded identify threshold, setting a timeout for ${reset_after} ms`);
|
||||
await delayFor(reset_after);
|
||||
}
|
||||
const gateway = `${res.url}/`;
|
||||
if (this.options.shardCount === 'auto') {
|
||||
this.emit(Events.DEBUG, `Using recommended shard count ${res.shards}`);
|
||||
this.options.shardCount = res.shards;
|
||||
this.options.totalShardCount = res.shards;
|
||||
}
|
||||
this.emit(Events.DEBUG, `Using gateway ${gateway}`);
|
||||
this.ws.connect(gateway);
|
||||
await new Promise((resolve, reject) => {
|
||||
const onready = () => {
|
||||
clearTimeout(timeout);
|
||||
this.removeListener(Events.DISCONNECT, ondisconnect);
|
||||
resolve();
|
||||
};
|
||||
const ondisconnect = event => {
|
||||
clearTimeout(timeout);
|
||||
this.removeListener(Events.READY, onready);
|
||||
this.destroy();
|
||||
if (WSCodes[event.code]) {
|
||||
reject(new Error(WSCodes[event.code]));
|
||||
}
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
this.removeListener(Events.READY, onready);
|
||||
this.removeListener(Events.DISCONNECT, ondisconnect);
|
||||
this.destroy();
|
||||
reject(new Error('WS_CONNECTION_TIMEOUT'));
|
||||
}, this.options.shardCount * 25e3);
|
||||
if (timeout.unref !== undefined) timeout.unref();
|
||||
this.once(Events.READY, onready);
|
||||
this.once(Events.DISCONNECT, ondisconnect);
|
||||
});
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,7 +263,8 @@ class Client extends BaseClient {
|
||||
*/
|
||||
destroy() {
|
||||
super.destroy();
|
||||
return this.manager.destroy();
|
||||
this.ws.destroy();
|
||||
this.token = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -386,22 +381,10 @@ class Client extends BaseClient {
|
||||
return super.toJSON({
|
||||
readyAt: false,
|
||||
broadcasts: false,
|
||||
pings: false,
|
||||
presences: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a ping to {@link Client#pings}.
|
||||
* @param {number} startTime Starting time of the ping
|
||||
* @private
|
||||
*/
|
||||
_pong(startTime) {
|
||||
this.pings.unshift(Date.now() - startTime);
|
||||
if (this.pings.length > 3) this.pings.length = 3;
|
||||
this.ws.lastHeartbeatAck = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval} on a script
|
||||
* with the client as `this`.
|
||||
@@ -419,17 +402,13 @@ class Client extends BaseClient {
|
||||
* @private
|
||||
*/
|
||||
_validateOptions(options = this.options) { // eslint-disable-line complexity
|
||||
if (typeof options.shardCount !== 'number' || isNaN(options.shardCount)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'shardCount', 'a number');
|
||||
if (options.shardCount !== 'auto' && (typeof options.shardCount !== 'number' || isNaN(options.shardCount))) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'shardCount', 'a number or "auto"');
|
||||
}
|
||||
if (typeof options.shardId !== 'number' || isNaN(options.shardId)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'shardId', 'a number');
|
||||
}
|
||||
if (options.shardCount < 0) throw new RangeError('CLIENT_INVALID_OPTION', 'shardCount', 'at least 0');
|
||||
if (options.shardId < 0) throw new RangeError('CLIENT_INVALID_OPTION', 'shardId', 'at least 0');
|
||||
if (options.shardId !== 0 && options.shardId >= options.shardCount) {
|
||||
throw new RangeError('CLIENT_INVALID_OPTION', 'shardId', 'less than shardCount');
|
||||
if (options.shards && typeof options.shards !== 'number' && !Array.isArray(options.shards)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'shards', 'a number or array');
|
||||
}
|
||||
if (options.shardCount < 1) throw new RangeError('CLIENT_INVALID_OPTION', 'shardCount', 'at least 1');
|
||||
if (typeof options.messageCacheMaxSize !== 'number' || isNaN(options.messageCacheMaxSize)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'messageCacheMaxSize', 'a number');
|
||||
}
|
||||
@@ -451,9 +430,6 @@ class Client extends BaseClient {
|
||||
if (typeof options.restSweepInterval !== 'number' || isNaN(options.restSweepInterval)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'restSweepInterval', 'a number');
|
||||
}
|
||||
if (typeof options.internalSharding !== 'boolean') {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'internalSharding', 'a boolean');
|
||||
}
|
||||
if (!(options.disabledEvents instanceof Array)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'disabledEvents', 'an Array');
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
const { Events, Status } = require('../util/Constants');
|
||||
const { Error } = require('../errors');
|
||||
|
||||
/**
|
||||
* Manages the state and background tasks of the client.
|
||||
* @private
|
||||
*/
|
||||
class ClientManager {
|
||||
constructor(client) {
|
||||
/**
|
||||
* The client that instantiated this Manager
|
||||
* @type {Client}
|
||||
*/
|
||||
this.client = client;
|
||||
|
||||
/**
|
||||
* The heartbeat interval
|
||||
* @type {?number}
|
||||
*/
|
||||
this.heartbeatInterval = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The status of the client
|
||||
* @readonly
|
||||
* @type {number}
|
||||
*/
|
||||
get status() {
|
||||
return this.connection ? this.connection.status : Status.IDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects the client to the WebSocket.
|
||||
* @param {string} token The authorization token
|
||||
* @param {Function} resolve Function to run when connection is successful
|
||||
* @param {Function} reject Function to run when connection fails
|
||||
*/
|
||||
connectToWebSocket(token, resolve, reject) {
|
||||
this.client.emit(Events.DEBUG, `Authenticated using token ${token}`);
|
||||
this.client.token = token;
|
||||
const timeout = this.client.setTimeout(() => reject(new Error('WS_CONNECTION_TIMEOUT')), 1000 * 300);
|
||||
this.client.api.gateway.get().then(async res => {
|
||||
if (this.client.options.presence != null) { // eslint-disable-line eqeqeq
|
||||
const presence = await this.client.presence._parse(this.client.options.presence);
|
||||
this.client.options.ws.presence = presence;
|
||||
this.client.presence.patch(presence);
|
||||
}
|
||||
const gateway = `${res.url}/`;
|
||||
this.client.emit(Events.DEBUG, `Using gateway ${gateway}`);
|
||||
this.client.ws.connect(gateway);
|
||||
this.client.ws.connection.once('error', reject);
|
||||
this.client.ws.connection.once('close', event => {
|
||||
if (event.code === 4004) reject(new Error('TOKEN_INVALID'));
|
||||
if (event.code === 4010) reject(new Error('SHARDING_INVALID'));
|
||||
if (event.code === 4011) reject(new Error('SHARDING_REQUIRED'));
|
||||
});
|
||||
this.client.once(Events.READY, () => {
|
||||
resolve(token);
|
||||
this.client.clearTimeout(timeout);
|
||||
});
|
||||
}, reject);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.client.ws.destroy();
|
||||
if (this.client.user) this.client.token = null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientManager;
|
||||
@@ -19,13 +19,17 @@ class ActionsManager {
|
||||
this.register(require('./GuildRoleCreate'));
|
||||
this.register(require('./GuildRoleDelete'));
|
||||
this.register(require('./GuildRoleUpdate'));
|
||||
this.register(require('./PresenceUpdate'));
|
||||
this.register(require('./UserUpdate'));
|
||||
this.register(require('./VoiceStateUpdate'));
|
||||
this.register(require('./GuildEmojiCreate'));
|
||||
this.register(require('./GuildEmojiDelete'));
|
||||
this.register(require('./GuildEmojiUpdate'));
|
||||
this.register(require('./GuildEmojisUpdate'));
|
||||
this.register(require('./GuildRolesPositionUpdate'));
|
||||
this.register(require('./GuildChannelsPositionUpdate'));
|
||||
this.register(require('./GuildIntegrationsUpdate'));
|
||||
this.register(require('./WebhooksUpdate'));
|
||||
}
|
||||
|
||||
register(Action) {
|
||||
|
||||
18
src/client/actions/GuildIntegrationsUpdate.js
Normal file
18
src/client/actions/GuildIntegrationsUpdate.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const Action = require('./Action');
|
||||
const { Events } = require('../../util/Constants');
|
||||
|
||||
class GuildIntegrationsUpdate extends Action {
|
||||
handle(data) {
|
||||
const client = this.client;
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
if (guild) client.emit(Events.GUILD_INTEGRATIONS_UPDATE, guild);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildIntegrationsUpdate;
|
||||
|
||||
/**
|
||||
* Emitted whenever a guild integration is updated
|
||||
* @event Client#guildIntegrationsUpdate
|
||||
* @param {Guild} guild The guild whose integrations were updated
|
||||
*/
|
||||
@@ -2,7 +2,7 @@ const Action = require('./Action');
|
||||
const { Events, Status } = require('../../util/Constants');
|
||||
|
||||
class GuildMemberRemoveAction extends Action {
|
||||
handle(data) {
|
||||
handle(data, shard) {
|
||||
const client = this.client;
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
let member = null;
|
||||
@@ -13,7 +13,7 @@ class GuildMemberRemoveAction extends Action {
|
||||
guild.voiceStates.delete(member.id);
|
||||
member.deleted = true;
|
||||
guild.members.remove(member.id);
|
||||
if (client.status === Status.READY) client.emit(Events.GUILD_MEMBER_REMOVE, member);
|
||||
if (shard.status === Status.READY) client.emit(Events.GUILD_MEMBER_REMOVE, member);
|
||||
}
|
||||
}
|
||||
return { guild, member };
|
||||
|
||||
@@ -10,7 +10,9 @@ class MessageCreateAction extends Action {
|
||||
if (existing) return { message: existing };
|
||||
const message = channel.messages.add(data);
|
||||
const user = message.author;
|
||||
const member = channel.guild ? channel.guild.member(user) : null;
|
||||
let member = null;
|
||||
if (message.member && channel.guild) member = channel.guild.members.add(message.member);
|
||||
else if (channel.guild) member = channel.guild.member(user);
|
||||
channel.lastMessageID = data.id;
|
||||
if (user) {
|
||||
user.lastMessageID = data.id;
|
||||
|
||||
38
src/client/actions/PresenceUpdate.js
Normal file
38
src/client/actions/PresenceUpdate.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const Action = require('./Action');
|
||||
const { Events } = require('../../util/Constants');
|
||||
|
||||
class PresenceUpdateAction extends Action {
|
||||
handle(data) {
|
||||
let cached = this.client.users.get(data.user.id);
|
||||
if (!cached && data.user.username) cached = this.client.users.add(data.user);
|
||||
if (!cached) return;
|
||||
|
||||
if (data.user && data.user.username) {
|
||||
if (!cached.equals(data.user)) this.client.actions.UserUpdate.handle(data);
|
||||
}
|
||||
|
||||
const guild = this.client.guilds.get(data.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
let member = guild.members.get(cached.id);
|
||||
if (!member && data.status !== 'offline') {
|
||||
member = guild.members.add({ user: cached, roles: data.roles, deaf: false, mute: false });
|
||||
this.client.emit(Events.GUILD_MEMBER_AVAILABLE, member);
|
||||
}
|
||||
|
||||
if (member) {
|
||||
if (this.client.listenerCount(Events.PRESENCE_UPDATE) === 0) {
|
||||
guild.presences.add(data);
|
||||
return;
|
||||
}
|
||||
const old = member._clone();
|
||||
if (member.presence) old.frozenPresence = member.presence._clone();
|
||||
guild.presences.add(data);
|
||||
this.client.emit(Events.PRESENCE_UPDATE, old, member);
|
||||
} else {
|
||||
guild.presences.add(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PresenceUpdateAction;
|
||||
@@ -5,19 +5,14 @@ class UserUpdateAction extends Action {
|
||||
handle(data) {
|
||||
const client = this.client;
|
||||
|
||||
if (client.user) {
|
||||
if (client.user.equals(data)) {
|
||||
return {
|
||||
old: client.user,
|
||||
updated: client.user,
|
||||
};
|
||||
}
|
||||
const newUser = client.users.get(data.user.id);
|
||||
const oldUser = newUser._update(data.user);
|
||||
|
||||
const oldUser = client.user._update(data);
|
||||
client.emit(Events.USER_UPDATE, oldUser, client.user);
|
||||
if (!oldUser.equals(newUser)) {
|
||||
client.emit(Events.USER_UPDATE, oldUser, newUser);
|
||||
return {
|
||||
old: oldUser,
|
||||
updated: client.user,
|
||||
updated: newUser,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
const { Events } = require('../../../../util/Constants');
|
||||
const VoiceState = require('../../../../structures/VoiceState');
|
||||
|
||||
class VoiceStateUpdateHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
const Action = require('./Action');
|
||||
const { Events } = require('../../util/Constants');
|
||||
const VoiceState = require('../../structures/VoiceState');
|
||||
|
||||
class VoiceStateUpdate extends Action {
|
||||
handle(data) {
|
||||
const client = this.client;
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
if (guild) {
|
||||
// Update the state
|
||||
@@ -42,4 +39,4 @@ class VoiceStateUpdateHandler extends AbstractHandler {
|
||||
* @param {VoiceState} newState The voice state after the update
|
||||
*/
|
||||
|
||||
module.exports = VoiceStateUpdateHandler;
|
||||
module.exports = VoiceStateUpdate;
|
||||
@@ -1,10 +1,9 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events } = require('../../../../util/Constants');
|
||||
const Action = require('./Action');
|
||||
const { Events } = require('../../util/Constants');
|
||||
|
||||
class WebhooksUpdate extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
class WebhooksUpdate extends Action {
|
||||
handle(data) {
|
||||
const client = this.client;
|
||||
const channel = client.channels.get(data.channel_id);
|
||||
if (channel) client.emit(Events.WEBHOOKS_UPDATE, channel);
|
||||
}
|
||||
@@ -169,7 +169,7 @@ class VoiceConnection extends EventEmitter {
|
||||
self_deaf: false,
|
||||
}, options);
|
||||
|
||||
this.client.ws.send({
|
||||
this.channel.guild.shard.send({
|
||||
op: OPCodes.VOICE_STATE_UPDATE,
|
||||
d: options,
|
||||
});
|
||||
|
||||
@@ -1,88 +1,281 @@
|
||||
const EventEmitter = require('events');
|
||||
const { Events, Status } = require('../../util/Constants');
|
||||
const WebSocketConnection = require('./WebSocketConnection');
|
||||
const WebSocketShard = require('./WebSocketShard');
|
||||
const { Events, Status, WSEvents } = require('../../util/Constants');
|
||||
const PacketHandlers = require('./handlers');
|
||||
|
||||
const BeforeReadyWhitelist = [
|
||||
WSEvents.READY,
|
||||
WSEvents.RESUMED,
|
||||
WSEvents.GUILD_CREATE,
|
||||
WSEvents.GUILD_DELETE,
|
||||
WSEvents.GUILD_MEMBERS_CHUNK,
|
||||
WSEvents.GUILD_MEMBER_ADD,
|
||||
WSEvents.GUILD_MEMBER_REMOVE,
|
||||
];
|
||||
|
||||
/**
|
||||
* WebSocket Manager of the client.
|
||||
* @private
|
||||
*/
|
||||
class WebSocketManager extends EventEmitter {
|
||||
class WebSocketManager {
|
||||
constructor(client) {
|
||||
super();
|
||||
/**
|
||||
* The client that instantiated this WebSocketManager
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
this.client = client;
|
||||
Object.defineProperty(this, 'client', { value: client });
|
||||
|
||||
/**
|
||||
* The WebSocket connection of this manager
|
||||
* @type {?WebSocketConnection}
|
||||
* The gateway this WebSocketManager uses.
|
||||
* @type {?string}
|
||||
*/
|
||||
this.connection = null;
|
||||
this.gateway = undefined;
|
||||
|
||||
/**
|
||||
* An array of shards spawned by this WebSocketManager.
|
||||
* @type {WebSocketShard[]}
|
||||
*/
|
||||
this.shards = [];
|
||||
|
||||
/**
|
||||
* An array of queued shards to be spawned by this WebSocketManager.
|
||||
* @type {Array<WebSocketShard|number|string>}
|
||||
* @private
|
||||
*/
|
||||
this.spawnQueue = [];
|
||||
|
||||
/**
|
||||
* Whether or not this WebSocketManager is currently spawning shards.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.spawning = false;
|
||||
|
||||
/**
|
||||
* An array of queued events before this WebSocketManager became ready.
|
||||
* @type {object[]}
|
||||
* @private
|
||||
*/
|
||||
this.packetQueue = [];
|
||||
|
||||
/**
|
||||
* The current status of this WebSocketManager.
|
||||
* @type {number}
|
||||
*/
|
||||
this.status = Status.IDLE;
|
||||
|
||||
/**
|
||||
* The current session limit of the client.
|
||||
* @type {?Object}
|
||||
* @prop {number} total Total number of identifies available
|
||||
* @prop {number} remaining Number of identifies remaining
|
||||
* @prop {number} reset_after Number of milliseconds after which the limit resets
|
||||
*/
|
||||
this.sessionStartLimit = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a heartbeat on the available connection.
|
||||
* @returns {void}
|
||||
* The average ping of all WebSocketShards
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
heartbeat() {
|
||||
if (!this.connection) return this.debug('No connection to heartbeat');
|
||||
return this.connection.heartbeat();
|
||||
get ping() {
|
||||
const sum = this.shards.reduce((a, b) => a + b.ping, 0);
|
||||
return sum / this.shards.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a debug event.
|
||||
* @param {string} message Debug message
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
debug(message) {
|
||||
return this.client.emit(Events.DEBUG, `[ws] ${message}`);
|
||||
this.client.emit(Events.DEBUG, `[connection] ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the client.
|
||||
* @returns {void} Whether or not destruction was successful
|
||||
* Handles the session identify rate limit for a shard.
|
||||
* @param {WebSocketShard} shard Shard to handle
|
||||
* @private
|
||||
*/
|
||||
destroy() {
|
||||
if (!this.connection) {
|
||||
this.debug('Attempted to destroy WebSocket but no connection exists!');
|
||||
async _handleSessionLimit(shard) {
|
||||
this.sessionStartLimit = await this.client.api.gateway.bot.get().then(r => r.session_start_limit);
|
||||
const { remaining, reset_after } = this.sessionStartLimit;
|
||||
if (remaining !== 0) {
|
||||
this.spawn();
|
||||
} else {
|
||||
shard.debug(`Exceeded identify threshold, setting a timeout for ${reset_after} ms`);
|
||||
setTimeout(() => this.spawn(), this.sessionStartLimit.reset_after);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to spawn WebSocketShards.
|
||||
* @param {?WebSocketShard|WebSocketShard[]|number|string} query The WebSocketShards to be spawned
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
spawn(query) {
|
||||
if (query !== undefined) {
|
||||
if (Array.isArray(query)) {
|
||||
for (const item of query) {
|
||||
if (!this.spawnQueue.includes(item)) this.spawnQueue.push(item);
|
||||
}
|
||||
} else if (!this.spawnQueue.includes(query)) {
|
||||
this.spawnQueue.push(query);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.spawning || !this.spawnQueue.length) return;
|
||||
|
||||
this.spawning = true;
|
||||
let item = this.spawnQueue.shift();
|
||||
|
||||
if (typeof item === 'string' && !isNaN(item)) item = Number(item);
|
||||
if (typeof item === 'number') {
|
||||
const shard = new WebSocketShard(this, item, this.shards[item]);
|
||||
this.shards[item] = shard;
|
||||
shard.once(Events.READY, () => {
|
||||
this.spawning = false;
|
||||
this.client.setTimeout(() => this._handleSessionLimit(shard), 5000);
|
||||
});
|
||||
shard.once(Events.INVALIDATED, () => {
|
||||
this.spawning = false;
|
||||
});
|
||||
} else if (item instanceof WebSocketShard) {
|
||||
item.reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection to a gateway.
|
||||
* @param {string} [gateway=this.gateway] The gateway to connect to
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
connect(gateway = this.gateway) {
|
||||
this.gateway = gateway;
|
||||
|
||||
if (typeof this.client.options.shards === 'number') {
|
||||
this.debug('Spawning 1 shard');
|
||||
this.spawn(this.client.options.shards);
|
||||
} else if (Array.isArray(this.client.options.shards)) {
|
||||
this.debug(`Spawning ${this.client.options.shards.length} shards`);
|
||||
for (let i = 0; i < this.client.options.shards.length; i++) {
|
||||
this.spawn(this.client.options.shards[i]);
|
||||
}
|
||||
} else {
|
||||
this.debug(`Spawning ${this.client.options.shardCount} shards`);
|
||||
for (let i = 0; i < this.client.options.shardCount; i++) {
|
||||
this.spawn(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a packet and queues it if this WebSocketManager is not ready.
|
||||
* @param {Object} packet The packet to be handled
|
||||
* @param {WebSocketShard} shard The shard that will handle this packet
|
||||
* @returns {boolean}
|
||||
* @private
|
||||
*/
|
||||
handlePacket(packet, shard) {
|
||||
if (packet && this.status !== Status.READY) {
|
||||
if (!BeforeReadyWhitelist.includes(packet.t)) {
|
||||
this.packetQueue.push({ packet, shardID: shard.id });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.packetQueue.length) {
|
||||
const item = this.packetQueue.shift();
|
||||
this.client.setImmediate(() => {
|
||||
this.handlePacket(item.packet, this.shards[item.shardID]);
|
||||
});
|
||||
}
|
||||
|
||||
if (packet && PacketHandlers[packet.t]) {
|
||||
PacketHandlers[packet.t](this.client, packet, shard);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the client is ready to be marked as ready.
|
||||
* @returns {boolean}
|
||||
* @private
|
||||
*/
|
||||
checkReady() {
|
||||
if (this.shards.filter(s => s).length !== this.client.options.shardCount ||
|
||||
this.shards.some(s => s && s.status !== Status.READY)) {
|
||||
return false;
|
||||
}
|
||||
return this.connection.destroy();
|
||||
|
||||
let unavailableGuilds = 0;
|
||||
for (const guild of this.client.guilds.values()) {
|
||||
if (!guild.available) unavailableGuilds++;
|
||||
}
|
||||
if (unavailableGuilds === 0) {
|
||||
this.status = Status.NEARLY;
|
||||
if (!this.client.options.fetchAllMembers) return this.triggerReady();
|
||||
// Fetch all members before marking self as ready
|
||||
const promises = this.client.guilds.map(g => g.members.fetch());
|
||||
Promise.all(promises)
|
||||
.then(() => this.triggerReady())
|
||||
.catch(e => {
|
||||
this.debug(`Failed to fetch all members before ready! ${e}`);
|
||||
this.triggerReady();
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a packet on the available WebSocket.
|
||||
* @param {Object} packet Packet to send
|
||||
* Causes the client to be marked as ready and emits the ready event.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
send(packet) {
|
||||
if (!this.connection) {
|
||||
this.debug('No connection to websocket');
|
||||
triggerReady() {
|
||||
if (this.status === Status.READY) {
|
||||
this.debug('Tried to mark self as ready, but already ready');
|
||||
return;
|
||||
}
|
||||
this.connection.send(packet);
|
||||
this.status = Status.READY;
|
||||
|
||||
/**
|
||||
* Emitted when the client becomes ready to start working.
|
||||
* @event Client#ready
|
||||
*/
|
||||
this.client.emit(Events.READY);
|
||||
|
||||
this.handlePacket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects the client to a gateway.
|
||||
* @param {string} gateway The gateway to connect to
|
||||
* @returns {boolean}
|
||||
* Broadcasts a message to every shard in this WebSocketManager.
|
||||
* @param {*} packet The packet to send
|
||||
*/
|
||||
connect(gateway) {
|
||||
if (!this.connection) {
|
||||
this.connection = new WebSocketConnection(this, gateway);
|
||||
return true;
|
||||
broadcast(packet) {
|
||||
for (const shard of this.shards) {
|
||||
if (!shard) continue;
|
||||
shard.send(packet);
|
||||
}
|
||||
switch (this.connection.status) {
|
||||
case Status.IDLE:
|
||||
case Status.DISCONNECTED:
|
||||
this.connection.connect(gateway, 5500);
|
||||
return true;
|
||||
default:
|
||||
this.debug(`Couldn't connect to ${gateway} as the websocket is at state ${this.connection.status}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys all shards.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
destroy() {
|
||||
this.gateway = undefined;
|
||||
// Lock calls to spawn
|
||||
this.spawning = true;
|
||||
|
||||
for (const shard of this.shards) {
|
||||
if (!shard) continue;
|
||||
shard.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
const EventEmitter = require('events');
|
||||
const { Events, OPCodes, Status, WSCodes } = require('../../util/Constants');
|
||||
const PacketManager = require('./packets/WebSocketPacketManager');
|
||||
const WebSocket = require('../../WebSocket');
|
||||
const { Status, Events, OPCodes, WSEvents, WSCodes } = require('../../util/Constants');
|
||||
let zlib;
|
||||
try {
|
||||
var zlib = require('zlib-sync');
|
||||
zlib = require('zlib-sync');
|
||||
if (!zlib.Inflate) zlib = require('pako');
|
||||
} catch (err) {
|
||||
zlib = require('pako');
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstracts a WebSocket connection with decoding/encoding for the Discord gateway.
|
||||
* @private
|
||||
* Represents a Shard's Websocket connection.
|
||||
*/
|
||||
class WebSocketConnection extends EventEmitter {
|
||||
/**
|
||||
* @param {WebSocketManager} manager The WebSocket manager
|
||||
* @param {string} gateway The WebSocket gateway to connect to
|
||||
*/
|
||||
constructor(manager, gateway) {
|
||||
class WebSocketShard extends EventEmitter {
|
||||
constructor(manager, id, oldShard) {
|
||||
super();
|
||||
|
||||
/**
|
||||
* The WebSocket Manager of this connection
|
||||
* @type {WebSocketManager}
|
||||
@@ -27,242 +23,240 @@ class WebSocketConnection extends EventEmitter {
|
||||
this.manager = manager;
|
||||
|
||||
/**
|
||||
* The client this belongs to
|
||||
* @type {Client}
|
||||
*/
|
||||
this.client = manager.client;
|
||||
|
||||
/**
|
||||
* The WebSocket connection itself
|
||||
* @type {WebSocket}
|
||||
*/
|
||||
this.ws = null;
|
||||
|
||||
/**
|
||||
* The current sequence of the WebSocket
|
||||
* The id of the this shard.
|
||||
* @type {number}
|
||||
*/
|
||||
this.sequence = -1;
|
||||
this.id = id;
|
||||
|
||||
/**
|
||||
* The current sessionID of the WebSocket
|
||||
* @type {string}
|
||||
*/
|
||||
this.sessionID = null;
|
||||
|
||||
/**
|
||||
* The current status of the client
|
||||
* @type {number}
|
||||
* The current status of the shard
|
||||
* @type {Status}
|
||||
*/
|
||||
this.status = Status.IDLE;
|
||||
|
||||
/**
|
||||
* The Packet Manager of the connection
|
||||
* @type {WebSocketPacketManager}
|
||||
*/
|
||||
this.packetManager = new PacketManager(this);
|
||||
|
||||
/**
|
||||
* The last time a ping was sent (a timestamp)
|
||||
* The current sequence of the WebSocket
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.lastPingTimestamp = 0;
|
||||
|
||||
/**
|
||||
* Contains the rate limit queue and metadata
|
||||
* @type {Object}
|
||||
*/
|
||||
this.ratelimit = {
|
||||
queue: [],
|
||||
remaining: 120,
|
||||
total: 120,
|
||||
time: 60e3,
|
||||
resetTimer: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Events that are disabled (will not be processed)
|
||||
* @type {Object}
|
||||
*/
|
||||
this.disabledEvents = {};
|
||||
for (const event of this.client.options.disabledEvents) this.disabledEvents[event] = true;
|
||||
this.sequence = oldShard ? oldShard.sequence : -1;
|
||||
|
||||
/**
|
||||
* The sequence on WebSocket close
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.closeSequence = 0;
|
||||
|
||||
/**
|
||||
* Whether or not the WebSocket is expecting to be closed
|
||||
* @type {boolean}
|
||||
* The current session id of the WebSocket
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.expectingClose = false;
|
||||
this.sessionID = oldShard && oldShard.sessionID;
|
||||
|
||||
this.inflate = null;
|
||||
this.connect(gateway);
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes the client to be marked as ready and emits the ready event.
|
||||
* @returns {void}
|
||||
*/
|
||||
triggerReady() {
|
||||
if (this.status === Status.READY) {
|
||||
this.debug('Tried to mark self as ready, but already ready');
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Emitted when the client becomes ready to start working.
|
||||
* @event Client#ready
|
||||
* Previous heartbeat pings of the websocket (most recent first, limited to three elements)
|
||||
* @type {number[]}
|
||||
*/
|
||||
this.status = Status.READY;
|
||||
this.client.emit(Events.READY);
|
||||
this.packetManager.handleQueue();
|
||||
this.pings = [];
|
||||
|
||||
/**
|
||||
* The last time a ping was sent (a timestamp)
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.lastPingTimestamp = -1;
|
||||
|
||||
/**
|
||||
* List of servers the shard is connected to
|
||||
* @type {string[]}
|
||||
* @private
|
||||
*/
|
||||
this.trace = [];
|
||||
|
||||
/**
|
||||
* Contains the rate limit queue and metadata
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.ratelimit = {
|
||||
queue: [],
|
||||
total: 120,
|
||||
remaining: 120,
|
||||
time: 60e3,
|
||||
timer: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* The WebSocket connection for the current shard
|
||||
* @type {?WebSocket}
|
||||
* @private
|
||||
*/
|
||||
this.ws = null;
|
||||
|
||||
/**
|
||||
* @external Inflate
|
||||
* @see {@link https://www.npmjs.com/package/zlib-sync}
|
||||
*/
|
||||
|
||||
/**
|
||||
* The compression to use
|
||||
* @type {?Inflate}
|
||||
* @private
|
||||
*/
|
||||
this.inflate = null;
|
||||
|
||||
this.connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the client is ready to be marked as ready.
|
||||
* @returns {void}
|
||||
* Average heartbeat ping of the websocket, obtained by averaging the WebSocketShard#pings property
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
checkIfReady() {
|
||||
if (this.status === Status.READY || this.status === Status.NEARLY) return false;
|
||||
let unavailableGuilds = 0;
|
||||
for (const guild of this.client.guilds.values()) {
|
||||
if (!guild.available) unavailableGuilds++;
|
||||
}
|
||||
if (unavailableGuilds === 0) {
|
||||
this.status = Status.NEARLY;
|
||||
if (!this.client.options.fetchAllMembers) return this.triggerReady();
|
||||
// Fetch all members before marking self as ready
|
||||
const promises = this.client.guilds.map(g => g.members.fetch());
|
||||
Promise.all(promises)
|
||||
.then(() => this.triggerReady())
|
||||
.catch(e => {
|
||||
this.debug(`Failed to fetch all members before ready! ${e}`);
|
||||
this.triggerReady();
|
||||
});
|
||||
}
|
||||
return true;
|
||||
get ping() {
|
||||
const sum = this.pings.reduce((a, b) => a + b, 0);
|
||||
return sum / this.pings.length;
|
||||
}
|
||||
|
||||
// Util
|
||||
/**
|
||||
* Emits a debug message.
|
||||
* Emits a debug event.
|
||||
* @param {string} message Debug message
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
debug(message) {
|
||||
if (message instanceof Error) message = message.stack;
|
||||
return this.manager.debug(`[connection] ${message}`);
|
||||
this.manager.debug(`[shard ${this.id}] ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the current WebSocket queue.
|
||||
* Sends a heartbeat or sets an interval for sending heartbeats.
|
||||
* @param {number} [time] If -1, clears the interval, any other number sets an interval
|
||||
* If no value is given, a heartbeat will be sent instantly
|
||||
* @private
|
||||
*/
|
||||
processQueue() {
|
||||
if (this.ratelimit.remaining === 0) return;
|
||||
if (this.ratelimit.queue.length === 0) return;
|
||||
if (this.ratelimit.remaining === this.ratelimit.total) {
|
||||
this.ratelimit.resetTimer = this.client.setTimeout(() => {
|
||||
this.ratelimit.remaining = this.ratelimit.total;
|
||||
this.processQueue();
|
||||
}, this.ratelimit.time);
|
||||
}
|
||||
while (this.ratelimit.remaining > 0) {
|
||||
const item = this.ratelimit.queue.shift();
|
||||
if (!item) return;
|
||||
this._send(item);
|
||||
this.ratelimit.remaining--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data, bypassing the queue.
|
||||
* @param {Object} data Packet to send
|
||||
* @returns {void}
|
||||
*/
|
||||
_send(data) {
|
||||
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
||||
this.debug(`Tried to send packet ${data} but no WebSocket is available!`);
|
||||
heartbeat(time) {
|
||||
if (!isNaN(time)) {
|
||||
if (time === -1) {
|
||||
this.debug('Clearing heartbeat interval');
|
||||
this.manager.client.clearInterval(this.heartbeatInterval);
|
||||
this.heartbeatInterval = null;
|
||||
} else {
|
||||
this.debug(`Setting a heartbeat interval for ${time}ms`);
|
||||
this.heartbeatInterval = this.manager.client.setInterval(() => this.heartbeat(), time);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.ws.send(WebSocket.pack(data));
|
||||
|
||||
this.debug('Sending a heartbeat');
|
||||
this.lastPingTimestamp = Date.now();
|
||||
this.send({
|
||||
op: OPCodes.HEARTBEAT,
|
||||
d: this.sequence,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds data to the queue to be sent.
|
||||
* @param {Object} data Packet to send
|
||||
* @returns {void}
|
||||
* Acknowledges a heartbeat.
|
||||
* @private
|
||||
*/
|
||||
send(data) {
|
||||
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
||||
this.debug(`Tried to send packet ${data} but no WebSocket is available!`);
|
||||
return;
|
||||
}
|
||||
this.ratelimit.queue.push(data);
|
||||
this.processQueue();
|
||||
ackHeartbeat() {
|
||||
const latency = Date.now() - this.lastPingTimestamp;
|
||||
this.debug(`Heartbeat acknowledged, latency of ${latency}ms`);
|
||||
this.pings.unshift(latency);
|
||||
if (this.pings.length > 3) this.pings.length = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a connection to a gateway.
|
||||
* @param {string} gateway The gateway to connect to
|
||||
* @param {number} [after=0] How long to wait before connecting
|
||||
* @param {boolean} [force=false] Whether or not to force a new connection even if one already exists
|
||||
* @returns {boolean}
|
||||
* Connects the shard to a gateway.
|
||||
* @private
|
||||
*/
|
||||
connect(gateway = this.gateway, after = 0, force = false) {
|
||||
if (after) return this.client.setTimeout(() => this.connect(gateway, 0, force), after); // eslint-disable-line
|
||||
if (this.ws && !force) {
|
||||
this.debug('WebSocket connection already exists');
|
||||
return false;
|
||||
} else if (typeof gateway !== 'string') {
|
||||
this.debug(`Tried to connect to an invalid gateway: ${gateway}`);
|
||||
return false;
|
||||
}
|
||||
connect() {
|
||||
this.inflate = new zlib.Inflate({
|
||||
chunkSize: 65535,
|
||||
flush: zlib.Z_SYNC_FLUSH,
|
||||
to: WebSocket.encoding === 'json' ? 'string' : '',
|
||||
});
|
||||
this.expectingClose = false;
|
||||
this.gateway = gateway;
|
||||
const gateway = this.manager.gateway;
|
||||
this.debug(`Connecting to ${gateway}`);
|
||||
const ws = this.ws = WebSocket.create(gateway, {
|
||||
v: this.client.options.ws.version,
|
||||
v: this.manager.client.options.ws.version,
|
||||
compress: 'zlib-stream',
|
||||
});
|
||||
ws.onmessage = this.onMessage.bind(this);
|
||||
ws.onopen = this.onOpen.bind(this);
|
||||
ws.onmessage = this.onMessage.bind(this);
|
||||
ws.onerror = this.onError.bind(this);
|
||||
ws.onclose = this.onClose.bind(this);
|
||||
this.status = Status.CONNECTING;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the connection.
|
||||
* Called whenever a packet is received
|
||||
* @param {Object} packet Packet received
|
||||
* @returns {boolean}
|
||||
* @private
|
||||
*/
|
||||
destroy() {
|
||||
const ws = this.ws;
|
||||
if (!ws) {
|
||||
this.debug('Attempted to destroy WebSocket but no connection exists!');
|
||||
onPacket(packet) {
|
||||
if (!packet) {
|
||||
this.debug('Received null packet');
|
||||
return false;
|
||||
}
|
||||
this.heartbeat(-1);
|
||||
this.expectingClose = true;
|
||||
ws.close(1000);
|
||||
this.packetManager.handleQueue();
|
||||
this.ws = null;
|
||||
this.status = Status.DISCONNECTED;
|
||||
this.ratelimit.remaining = this.ratelimit.total;
|
||||
return true;
|
||||
|
||||
this.manager.client.emit(Events.RAW, packet, this.id);
|
||||
|
||||
switch (packet.t) {
|
||||
case WSEvents.READY:
|
||||
this.sessionID = packet.d.session_id;
|
||||
this.trace = packet.d._trace;
|
||||
this.status = Status.READY;
|
||||
this.debug(`READY ${this.trace.join(' -> ')} ${this.sessionID}`);
|
||||
this.heartbeat();
|
||||
break;
|
||||
case WSEvents.RESUMED: {
|
||||
this.trace = packet.d._trace;
|
||||
this.status = Status.READY;
|
||||
const replayed = packet.s - this.sequence;
|
||||
this.debug(`RESUMED ${this.trace.join(' -> ')} | replayed ${replayed} events.`);
|
||||
this.heartbeat();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.s > this.sequence) this.sequence = packet.s;
|
||||
|
||||
switch (packet.op) {
|
||||
case OPCodes.HELLO:
|
||||
this.identify();
|
||||
return this.heartbeat(packet.d.heartbeat_interval);
|
||||
case OPCodes.RECONNECT:
|
||||
return this.reconnect();
|
||||
case OPCodes.INVALID_SESSION:
|
||||
if (!packet.d) this.sessionID = null;
|
||||
this.sequence = -1;
|
||||
this.debug('Session invalidated');
|
||||
return this.reconnect(Events.INVALIDATED);
|
||||
case OPCodes.HEARTBEAT_ACK:
|
||||
return this.ackHeartbeat();
|
||||
case OPCodes.HEARTBEAT:
|
||||
return this.heartbeat();
|
||||
default:
|
||||
return this.manager.handlePacket(packet, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a connection is opened to the gateway.
|
||||
* @param {Event} event Received open event
|
||||
* @private
|
||||
*/
|
||||
onOpen() {
|
||||
this.debug('Connection open');
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a message is received.
|
||||
* @param {Event} event Event received
|
||||
* @private
|
||||
*/
|
||||
onMessage({ data }) {
|
||||
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
|
||||
@@ -278,89 +272,40 @@ class WebSocketConnection extends EventEmitter {
|
||||
let packet;
|
||||
try {
|
||||
packet = WebSocket.unpack(this.inflate.result);
|
||||
this.manager.client.emit(Events.RAW, packet);
|
||||
} catch (err) {
|
||||
this.client.emit('debug', err);
|
||||
this.manager.client.emit(Events.ERROR, err);
|
||||
return;
|
||||
}
|
||||
if (packet.t === 'READY') {
|
||||
/**
|
||||
* Emitted when a shard becomes ready
|
||||
* @event WebSocketShard#ready
|
||||
*/
|
||||
this.emit(Events.READY);
|
||||
|
||||
/**
|
||||
* Emitted when a shard becomes ready
|
||||
* @event Client#shardReady
|
||||
* @param {number} shardID The id of the shard
|
||||
*/
|
||||
this.manager.client.emit(Events.SHARD_READY, this.id);
|
||||
}
|
||||
this.onPacket(packet);
|
||||
if (this.client.listenerCount('raw')) this.client.emit('raw', packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current sequence of the connection.
|
||||
* @param {number} s New sequence
|
||||
*/
|
||||
setSequence(s) {
|
||||
this.sequence = s > this.sequence ? s : this.sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a packet is received.
|
||||
* @param {Object} packet Received packet
|
||||
* @returns {boolean}
|
||||
*/
|
||||
onPacket(packet) {
|
||||
if (!packet) {
|
||||
this.debug('Received null packet');
|
||||
return false;
|
||||
}
|
||||
switch (packet.op) {
|
||||
case OPCodes.HELLO:
|
||||
return this.heartbeat(packet.d.heartbeat_interval);
|
||||
case OPCodes.RECONNECT:
|
||||
return this.reconnect();
|
||||
case OPCodes.INVALID_SESSION:
|
||||
if (!packet.d) this.sessionID = null;
|
||||
this.sequence = -1;
|
||||
this.debug('Session invalidated -- will identify with a new session');
|
||||
return this.identify(packet.d ? 2500 : 0);
|
||||
case OPCodes.HEARTBEAT_ACK:
|
||||
return this.ackHeartbeat();
|
||||
case OPCodes.HEARTBEAT:
|
||||
return this.heartbeat();
|
||||
default:
|
||||
return this.packetManager.handle(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a connection is opened to the gateway.
|
||||
* @param {Event} event Received open event
|
||||
*/
|
||||
onOpen(event) {
|
||||
if (event && event.target && event.target.url) this.gateway = event.target.url;
|
||||
this.debug(`Connected to gateway ${this.gateway}`);
|
||||
this.identify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes a reconnection to the gateway.
|
||||
*/
|
||||
reconnect() {
|
||||
this.debug('Attempting to reconnect in 5500ms...');
|
||||
/**
|
||||
* Emitted whenever the client tries to reconnect to the WebSocket.
|
||||
* @event Client#reconnecting
|
||||
*/
|
||||
this.client.emit(Events.RECONNECTING);
|
||||
this.connect(this.gateway, 5500, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever an error occurs with the WebSocket.
|
||||
* @param {Error} error The error that occurred
|
||||
* @private
|
||||
*/
|
||||
onError(error) {
|
||||
if (error && error.message === 'uWs client connection error') {
|
||||
this.reconnect();
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Emitted whenever the client's WebSocket encounters a connection error.
|
||||
* @event Client#error
|
||||
* @param {Error} error The encountered error
|
||||
*/
|
||||
this.client.emit(Events.ERROR, error);
|
||||
this.emit(Events.INVALIDATED);
|
||||
this.manager.client.emit(Events.ERROR, error);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -371,90 +316,50 @@ class WebSocketConnection extends EventEmitter {
|
||||
/**
|
||||
* Called whenever a connection to the gateway is closed.
|
||||
* @param {CloseEvent} event Close event that was received
|
||||
* @private
|
||||
*/
|
||||
onClose(event) {
|
||||
this.debug(`${this.expectingClose ? 'Client' : 'Server'} closed the WebSocket connection: ${event.code}`);
|
||||
this.closeSequence = this.sequence;
|
||||
// Reset the state before trying to fix anything
|
||||
this.emit('close', event);
|
||||
this.heartbeat(-1);
|
||||
// Should we reconnect?
|
||||
if (event.code === 1000 ? this.expectingClose : WSCodes[event.code]) {
|
||||
this.expectingClose = false;
|
||||
/**
|
||||
* Emitted when the client's WebSocket disconnects and will no longer attempt to reconnect.
|
||||
* @event Client#disconnect
|
||||
* @param {CloseEvent} event The WebSocket close event
|
||||
* @param {number} shardID The shard that disconnected
|
||||
*/
|
||||
this.client.emit(Events.DISCONNECT, event);
|
||||
this.manager.client.emit(Events.DISCONNECT, event, this.id);
|
||||
|
||||
this.debug(WSCodes[event.code]);
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
this.expectingClose = false;
|
||||
this.reconnect();
|
||||
this.reconnect(Events.INVALIDATED);
|
||||
}
|
||||
|
||||
// Heartbeat
|
||||
/**
|
||||
* Acknowledges a heartbeat.
|
||||
*/
|
||||
ackHeartbeat() {
|
||||
this.debug(`Heartbeat acknowledged, latency of ${Date.now() - this.lastPingTimestamp}ms`);
|
||||
this.client._pong(this.lastPingTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a heartbeat or sets an interval for sending heartbeats.
|
||||
* @param {number} [time] If -1, clears the interval, any other number sets an interval
|
||||
* If no value is given, a heartbeat will be sent instantly
|
||||
*/
|
||||
heartbeat(time) {
|
||||
if (!isNaN(time)) {
|
||||
if (time === -1) {
|
||||
this.debug('Clearing heartbeat interval');
|
||||
this.client.clearInterval(this.heartbeatInterval);
|
||||
this.heartbeatInterval = null;
|
||||
} else {
|
||||
this.debug(`Setting a heartbeat interval for ${time}ms`);
|
||||
this.heartbeatInterval = this.client.setInterval(() => this.heartbeat(), time);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.debug('Sending a heartbeat');
|
||||
this.lastPingTimestamp = Date.now();
|
||||
this.send({
|
||||
op: OPCodes.HEARTBEAT,
|
||||
d: this.sequence,
|
||||
});
|
||||
}
|
||||
|
||||
// Identification
|
||||
/**
|
||||
* Identifies the client on a connection.
|
||||
* @param {number} [after] How long to wait before identifying
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
identify(after) {
|
||||
if (after) return this.client.setTimeout(this.identify.bind(this), after);
|
||||
identify() {
|
||||
return this.sessionID ? this.identifyResume() : this.identifyNew();
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies as a new connection on the gateway.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
identifyNew() {
|
||||
if (!this.client.token) {
|
||||
if (!this.manager.client.token) {
|
||||
this.debug('No token available to identify a new session with');
|
||||
return;
|
||||
}
|
||||
// Clone the generic payload and assign the token
|
||||
const d = Object.assign({ token: this.client.token }, this.client.options.ws);
|
||||
const d = Object.assign({ token: this.manager.client.token }, this.manager.client.options.ws);
|
||||
|
||||
// Sharding stuff
|
||||
const { shardId, shardCount } = this.client.options;
|
||||
if (shardCount > 0) d.shard = [Number(shardId), Number(shardCount)];
|
||||
const { totalShardCount } = this.manager.client.options;
|
||||
d.shard = [this.id, Number(totalShardCount)];
|
||||
|
||||
// Send the payload
|
||||
this.debug('Identifying as a new session');
|
||||
@@ -464,6 +369,7 @@ class WebSocketConnection extends EventEmitter {
|
||||
/**
|
||||
* Resumes a session on the gateway.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
identifyResume() {
|
||||
if (!this.sessionID) {
|
||||
@@ -473,7 +379,7 @@ class WebSocketConnection extends EventEmitter {
|
||||
this.debug(`Attempting to resume session ${this.sessionID}`);
|
||||
|
||||
const d = {
|
||||
token: this.client.token,
|
||||
token: this.manager.client.token,
|
||||
session_id: this.sessionID,
|
||||
seq: this.sequence,
|
||||
};
|
||||
@@ -483,6 +389,85 @@ class WebSocketConnection extends EventEmitter {
|
||||
d,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebSocketConnection;
|
||||
/**
|
||||
* Adds data to the queue to be sent.
|
||||
* @param {Object} data Packet to send
|
||||
* @returns {void}
|
||||
*/
|
||||
send(data) {
|
||||
this.ratelimit.queue.push(data);
|
||||
this.processQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data, bypassing the queue.
|
||||
* @param {Object} data Packet to send
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
_send(data) {
|
||||
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
||||
this.debug(`Tried to send packet ${data} but no WebSocket is available!`);
|
||||
return;
|
||||
}
|
||||
this.ws.send(WebSocket.pack(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the current WebSocket queue.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
processQueue() {
|
||||
if (this.ratelimit.remaining === 0) return;
|
||||
if (this.ratelimit.queue.length === 0) return;
|
||||
if (this.ratelimit.remaining === this.ratelimit.total) {
|
||||
this.ratelimit.resetTimer = this.manager.client.setTimeout(() => {
|
||||
this.ratelimit.remaining = this.ratelimit.total;
|
||||
this.processQueue();
|
||||
}, this.ratelimit.time);
|
||||
}
|
||||
while (this.ratelimit.remaining > 0) {
|
||||
const item = this.ratelimit.queue.shift();
|
||||
if (!item) return;
|
||||
this._send(item);
|
||||
this.ratelimit.remaining--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a shard reconnect.
|
||||
* @param {?string} [event] The event for the shard to emit
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
reconnect(event) {
|
||||
this.heartbeat(-1);
|
||||
this.status = Status.RECONNECTING;
|
||||
|
||||
/**
|
||||
* Emitted whenever a shard tries to reconnect to the WebSocket.
|
||||
* @event Client#reconnecting
|
||||
*/
|
||||
this.manager.client.emit(Events.RECONNECTING, this.id);
|
||||
|
||||
if (event === Events.INVALIDATED) this.emit(event);
|
||||
this.manager.spawn(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the current shard and terminates its connection.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
destroy() {
|
||||
this.heartbeat(-1);
|
||||
this.expectingClose = true;
|
||||
if (this.ws) this.ws.close(1000);
|
||||
this.ws = null;
|
||||
this.status = Status.DISCONNECTED;
|
||||
this.ratelimit.remaining = this.ratelimit.total;
|
||||
}
|
||||
}
|
||||
module.exports = WebSocketShard;
|
||||
3
src/client/websocket/handlers/CHANNEL_CREATE.js
Normal file
3
src/client/websocket/handlers/CHANNEL_CREATE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.ChannelCreate.handle(packet.d);
|
||||
};
|
||||
3
src/client/websocket/handlers/CHANNEL_DELETE.js
Normal file
3
src/client/websocket/handlers/CHANNEL_DELETE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.ChannelDelete.handle(packet.d);
|
||||
};
|
||||
20
src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js
Normal file
20
src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
const channel = client.channels.get(data.channel_id);
|
||||
const time = new Date(data.last_pin_timestamp);
|
||||
|
||||
if (channel && time) {
|
||||
// Discord sends null for last_pin_timestamp if the last pinned message was removed
|
||||
channel.lastPinTimestamp = time.getTime() || null;
|
||||
|
||||
/**
|
||||
* Emitted whenever the pins of a channel are updated. Due to the nature of the WebSocket event,
|
||||
* not much information can be provided easily here - you need to manually check the pins yourself.
|
||||
* @event Client#channelPinsUpdate
|
||||
* @param {DMChannel|GroupDMChannel|TextChannel} channel The channel that the pins update occured in
|
||||
* @param {Date} time The time of the pins update
|
||||
*/
|
||||
client.emit(Events.CHANNEL_PINS_UPDATE, channel, time);
|
||||
}
|
||||
};
|
||||
15
src/client/websocket/handlers/CHANNEL_UPDATE.js
Normal file
15
src/client/websocket/handlers/CHANNEL_UPDATE.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
const { old, updated } = client.actions.ChannelUpdate.handle(packet.d);
|
||||
if (old && updated) {
|
||||
/**
|
||||
* Emitted whenever a channel is updated - e.g. name change, topic change.
|
||||
* @event Client#channelUpdate
|
||||
* @param {DMChannel|GroupDMChannel|GuildChannel} oldChannel The channel before the update
|
||||
* @param {DMChannel|GroupDMChannel|GuildChannel} newChannel The channel after the update
|
||||
*/
|
||||
client.emit(Events.CHANNEL_UPDATE, old, updated);
|
||||
}
|
||||
};
|
||||
|
||||
14
src/client/websocket/handlers/GUILD_BAN_ADD.js
Normal file
14
src/client/websocket/handlers/GUILD_BAN_ADD.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
const user = client.users.get(data.user.id);
|
||||
|
||||
/**
|
||||
* Emitted whenever a member is banned from a guild.
|
||||
* @event Client#guildBanAdd
|
||||
* @param {Guild} guild The guild that the ban occurred in
|
||||
* @param {User} user The user that was banned
|
||||
*/
|
||||
if (guild && user) client.emit(Events.GUILD_BAN_ADD, guild, user);
|
||||
};
|
||||
3
src/client/websocket/handlers/GUILD_BAN_REMOVE.js
Normal file
3
src/client/websocket/handlers/GUILD_BAN_REMOVE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildBanRemove.handle(packet.d);
|
||||
};
|
||||
26
src/client/websocket/handlers/GUILD_CREATE.js
Normal file
26
src/client/websocket/handlers/GUILD_CREATE.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const { Events, Status } = require('../../../util/Constants');
|
||||
|
||||
module.exports = async (client, { d: data }, shard) => {
|
||||
let guild = client.guilds.get(data.id);
|
||||
if (guild) {
|
||||
if (!guild.available && !data.unavailable) {
|
||||
// A newly available guild
|
||||
guild._patch(data);
|
||||
client.ws.checkReady();
|
||||
}
|
||||
} else {
|
||||
// A new guild
|
||||
data.shardID = shard.id;
|
||||
guild = client.guilds.add(data);
|
||||
const emitEvent = client.ws.status === Status.READY;
|
||||
if (emitEvent) {
|
||||
/**
|
||||
* Emitted whenever the client joins a guild.
|
||||
* @event Client#guildCreate
|
||||
* @param {Guild} guild The created guild
|
||||
*/
|
||||
if (client.options.fetchAllMembers) await guild.members.fetch();
|
||||
client.emit(Events.GUILD_CREATE, guild);
|
||||
}
|
||||
}
|
||||
};
|
||||
3
src/client/websocket/handlers/GUILD_DELETE.js
Normal file
3
src/client/websocket/handlers/GUILD_DELETE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildDelete.handle(packet.d);
|
||||
};
|
||||
3
src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js
Normal file
3
src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildEmojisUpdate.handle(packet.d);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildIntegrationsUpdate.handle(packet.d);
|
||||
};
|
||||
17
src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js
Normal file
17
src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const { Events } = require('../../../util/Constants');
|
||||
const Collection = require('../../../util/Collection');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
if (!guild) return;
|
||||
const members = new Collection();
|
||||
|
||||
for (const member of data.members) members.set(member.user.id, guild.members.add(member));
|
||||
/**
|
||||
* Emitted whenever a chunk of guild members is received (all members come from the same guild).
|
||||
* @event Client#guildMembersChunk
|
||||
* @param {Collection<Snowflake, GuildMember>} members The members in the chunk
|
||||
* @param {Guild} guild The guild related to the member chunk
|
||||
*/
|
||||
client.emit(Events.GUILD_MEMBERS_CHUNK, members, guild);
|
||||
};
|
||||
17
src/client/websocket/handlers/GUILD_MEMBER_ADD.js
Normal file
17
src/client/websocket/handlers/GUILD_MEMBER_ADD.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const { Events, Status } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }, shard) => {
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
if (guild) {
|
||||
guild.memberCount++;
|
||||
const member = guild.members.add(data);
|
||||
if (shard.status === Status.READY) {
|
||||
/**
|
||||
* Emitted whenever a user joins a guild.
|
||||
* @event Client#guildMemberAdd
|
||||
* @param {GuildMember} member The member that has joined a guild
|
||||
*/
|
||||
client.emit(Events.GUILD_MEMBER_ADD, member);
|
||||
}
|
||||
}
|
||||
};
|
||||
3
src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js
Normal file
3
src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet, shard) => {
|
||||
client.actions.GuildMemberRemove.handle(packet.d, shard);
|
||||
};
|
||||
20
src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js
Normal file
20
src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const { Status, Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }, shard) => {
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
if (guild) {
|
||||
const member = guild.members.get(data.user.id);
|
||||
if (member) {
|
||||
const old = member._update(data);
|
||||
if (shard.status === Status.READY) {
|
||||
/**
|
||||
* Emitted whenever a guild member changes - i.e. new role, removed role, nickname.
|
||||
* @event Client#guildMemberUpdate
|
||||
* @param {GuildMember} oldMember The member before the update
|
||||
* @param {GuildMember} newMember The member after the update
|
||||
*/
|
||||
client.emit(Events.GUILD_MEMBER_UPDATE, old, member);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
3
src/client/websocket/handlers/GUILD_ROLE_CREATE.js
Normal file
3
src/client/websocket/handlers/GUILD_ROLE_CREATE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildRoleCreate.handle(packet.d);
|
||||
};
|
||||
3
src/client/websocket/handlers/GUILD_ROLE_DELETE.js
Normal file
3
src/client/websocket/handlers/GUILD_ROLE_DELETE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildRoleDelete.handle(packet.d);
|
||||
};
|
||||
3
src/client/websocket/handlers/GUILD_ROLE_UPDATE.js
Normal file
3
src/client/websocket/handlers/GUILD_ROLE_UPDATE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildRoleUpdate.handle(packet.d);
|
||||
};
|
||||
3
src/client/websocket/handlers/GUILD_SYNC.js
Normal file
3
src/client/websocket/handlers/GUILD_SYNC.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildSync.handle(packet.d);
|
||||
};
|
||||
3
src/client/websocket/handlers/GUILD_UPDATE.js
Normal file
3
src/client/websocket/handlers/GUILD_UPDATE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildUpdate.handle(packet.d);
|
||||
};
|
||||
3
src/client/websocket/handlers/MESSAGE_CREATE.js
Normal file
3
src/client/websocket/handlers/MESSAGE_CREATE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageCreate.handle(packet.d);
|
||||
};
|
||||
3
src/client/websocket/handlers/MESSAGE_DELETE.js
Normal file
3
src/client/websocket/handlers/MESSAGE_DELETE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageDelete.handle(packet.d);
|
||||
};
|
||||
3
src/client/websocket/handlers/MESSAGE_DELETE_BULK.js
Normal file
3
src/client/websocket/handlers/MESSAGE_DELETE_BULK.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageDeleteBulk.handle(packet.d);
|
||||
};
|
||||
6
src/client/websocket/handlers/MESSAGE_REACTION_ADD.js
Normal file
6
src/client/websocket/handlers/MESSAGE_REACTION_ADD.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
const { user, reaction } = client.actions.MessageReactionAdd.handle(packet.d);
|
||||
if (reaction) client.emit(Events.MESSAGE_REACTION_ADD, reaction, user);
|
||||
};
|
||||
3
src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js
Normal file
3
src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageReactionRemove.handle(packet.d);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageReactionRemoveAll.handle(packet.d);
|
||||
};
|
||||
14
src/client/websocket/handlers/MESSAGE_UPDATE.js
Normal file
14
src/client/websocket/handlers/MESSAGE_UPDATE.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
const { old, updated } = client.actions.MessageUpdate.handle(packet.d);
|
||||
if (old && updated) {
|
||||
/**
|
||||
* Emitted whenever a message is updated - e.g. embed or content change.
|
||||
* @event Client#messageUpdate
|
||||
* @param {Message} oldMessage The message before the update
|
||||
* @param {Message} newMessage The message after the update
|
||||
*/
|
||||
client.emit(Events.MESSAGE_UPDATE, old, updated);
|
||||
}
|
||||
};
|
||||
3
src/client/websocket/handlers/PRESENCE_UPDATE.js
Normal file
3
src/client/websocket/handlers/PRESENCE_UPDATE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.PresenceUpdate.handle(packet.d);
|
||||
};
|
||||
16
src/client/websocket/handlers/READY.js
Normal file
16
src/client/websocket/handlers/READY.js
Normal file
@@ -0,0 +1,16 @@
|
||||
let ClientUser;
|
||||
|
||||
module.exports = (client, { d: data }, shard) => {
|
||||
if (!ClientUser) ClientUser = require('../../../structures/ClientUser');
|
||||
const clientUser = new ClientUser(client, data.user);
|
||||
client.user = clientUser;
|
||||
client.readyAt = new Date();
|
||||
client.users.set(clientUser.id, clientUser);
|
||||
|
||||
for (const guild of data.guilds) {
|
||||
guild.shardID = shard.id;
|
||||
client.guilds.add(guild);
|
||||
}
|
||||
|
||||
client.ws.checkReady();
|
||||
};
|
||||
12
src/client/websocket/handlers/RESUMED.js
Normal file
12
src/client/websocket/handlers/RESUMED.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, packet, shard) => {
|
||||
const replayed = shard.sequence - shard.closeSequence;
|
||||
/**
|
||||
* Emitted when the client gateway resumes.
|
||||
* @event Client#resume
|
||||
* @param {number} replayed The number of events that were replayed
|
||||
* @param {number} shardID The ID of the shard that resumed
|
||||
*/
|
||||
client.emit(Events.RESUMED, replayed, shard.id);
|
||||
};
|
||||
16
src/client/websocket/handlers/TYPING_START.js
Normal file
16
src/client/websocket/handlers/TYPING_START.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const { Events } = require('../../../util/Constants');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
const channel = client.channels.get(data.channel_id);
|
||||
const user = client.users.get(data.user_id);
|
||||
|
||||
if (channel && user) {
|
||||
/**
|
||||
* Emitted whenever a user starts typing in a channel.
|
||||
* @event Client#typingStart
|
||||
* @param {Channel} channel The channel the user started typing in
|
||||
* @param {User} user The user that started typing
|
||||
*/
|
||||
client.emit(Events.TYPING_START, channel, user);
|
||||
}
|
||||
};
|
||||
3
src/client/websocket/handlers/USER_UPDATE.js
Normal file
3
src/client/websocket/handlers/USER_UPDATE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.UserUpdate.handle(packet.d);
|
||||
};
|
||||
3
src/client/websocket/handlers/VOICE_SERVER_UPDATE.js
Normal file
3
src/client/websocket/handlers/VOICE_SERVER_UPDATE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.emit('self.voiceServer', packet.d);
|
||||
};
|
||||
3
src/client/websocket/handlers/VOICE_STATE_UPDATE.js
Normal file
3
src/client/websocket/handlers/VOICE_STATE_UPDATE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.VoiceStateUpdate.handle(packet.d);
|
||||
};
|
||||
3
src/client/websocket/handlers/WEBHOOKS_UPDATE.js
Normal file
3
src/client/websocket/handlers/WEBHOOKS_UPDATE.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.WebhooksUpdate.handle(packet.d);
|
||||
};
|
||||
11
src/client/websocket/handlers/index.js
Normal file
11
src/client/websocket/handlers/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const { WSEvents } = require('../../../util/Constants');
|
||||
|
||||
const handlers = {};
|
||||
|
||||
for (const name of Object.keys(WSEvents)) {
|
||||
try {
|
||||
handlers[name] = require(`./${name}.js`);
|
||||
} catch (err) {} // eslint-disable-line no-empty
|
||||
}
|
||||
|
||||
module.exports = handlers;
|
||||
@@ -1,104 +0,0 @@
|
||||
const { OPCodes, Status, WSEvents } = require('../../../util/Constants');
|
||||
|
||||
const BeforeReadyWhitelist = [
|
||||
WSEvents.READY,
|
||||
WSEvents.RESUMED,
|
||||
WSEvents.GUILD_CREATE,
|
||||
WSEvents.GUILD_DELETE,
|
||||
WSEvents.GUILD_MEMBERS_CHUNK,
|
||||
WSEvents.GUILD_MEMBER_ADD,
|
||||
WSEvents.GUILD_MEMBER_REMOVE,
|
||||
];
|
||||
|
||||
class WebSocketPacketManager {
|
||||
constructor(connection) {
|
||||
this.ws = connection;
|
||||
this.handlers = {};
|
||||
this.queue = [];
|
||||
|
||||
this.register(WSEvents.READY, require('./handlers/Ready'));
|
||||
this.register(WSEvents.RESUMED, require('./handlers/Resumed'));
|
||||
this.register(WSEvents.GUILD_CREATE, require('./handlers/GuildCreate'));
|
||||
this.register(WSEvents.GUILD_DELETE, require('./handlers/GuildDelete'));
|
||||
this.register(WSEvents.GUILD_UPDATE, require('./handlers/GuildUpdate'));
|
||||
this.register(WSEvents.GUILD_BAN_ADD, require('./handlers/GuildBanAdd'));
|
||||
this.register(WSEvents.GUILD_BAN_REMOVE, require('./handlers/GuildBanRemove'));
|
||||
this.register(WSEvents.GUILD_MEMBER_ADD, require('./handlers/GuildMemberAdd'));
|
||||
this.register(WSEvents.GUILD_MEMBER_REMOVE, require('./handlers/GuildMemberRemove'));
|
||||
this.register(WSEvents.GUILD_MEMBER_UPDATE, require('./handlers/GuildMemberUpdate'));
|
||||
this.register(WSEvents.GUILD_ROLE_CREATE, require('./handlers/GuildRoleCreate'));
|
||||
this.register(WSEvents.GUILD_ROLE_DELETE, require('./handlers/GuildRoleDelete'));
|
||||
this.register(WSEvents.GUILD_ROLE_UPDATE, require('./handlers/GuildRoleUpdate'));
|
||||
this.register(WSEvents.GUILD_EMOJIS_UPDATE, require('./handlers/GuildEmojisUpdate'));
|
||||
this.register(WSEvents.GUILD_MEMBERS_CHUNK, require('./handlers/GuildMembersChunk'));
|
||||
this.register(WSEvents.GUILD_INTEGRATIONS_UPDATE, require('./handlers/GuildIntegrationsUpdate'));
|
||||
this.register(WSEvents.CHANNEL_CREATE, require('./handlers/ChannelCreate'));
|
||||
this.register(WSEvents.CHANNEL_DELETE, require('./handlers/ChannelDelete'));
|
||||
this.register(WSEvents.CHANNEL_UPDATE, require('./handlers/ChannelUpdate'));
|
||||
this.register(WSEvents.CHANNEL_PINS_UPDATE, require('./handlers/ChannelPinsUpdate'));
|
||||
this.register(WSEvents.PRESENCE_UPDATE, require('./handlers/PresenceUpdate'));
|
||||
this.register(WSEvents.USER_UPDATE, require('./handlers/UserUpdate'));
|
||||
this.register(WSEvents.VOICE_STATE_UPDATE, require('./handlers/VoiceStateUpdate'));
|
||||
this.register(WSEvents.TYPING_START, require('./handlers/TypingStart'));
|
||||
this.register(WSEvents.MESSAGE_CREATE, require('./handlers/MessageCreate'));
|
||||
this.register(WSEvents.MESSAGE_DELETE, require('./handlers/MessageDelete'));
|
||||
this.register(WSEvents.MESSAGE_UPDATE, require('./handlers/MessageUpdate'));
|
||||
this.register(WSEvents.MESSAGE_DELETE_BULK, require('./handlers/MessageDeleteBulk'));
|
||||
this.register(WSEvents.VOICE_SERVER_UPDATE, require('./handlers/VoiceServerUpdate'));
|
||||
this.register(WSEvents.MESSAGE_REACTION_ADD, require('./handlers/MessageReactionAdd'));
|
||||
this.register(WSEvents.MESSAGE_REACTION_REMOVE, require('./handlers/MessageReactionRemove'));
|
||||
this.register(WSEvents.MESSAGE_REACTION_REMOVE_ALL, require('./handlers/MessageReactionRemoveAll'));
|
||||
this.register(WSEvents.WEBHOOKS_UPDATE, require('./handlers/WebhooksUpdate'));
|
||||
}
|
||||
|
||||
get client() {
|
||||
return this.ws.client;
|
||||
}
|
||||
|
||||
register(event, Handler) {
|
||||
this.handlers[event] = new Handler(this);
|
||||
}
|
||||
|
||||
handleQueue() {
|
||||
this.queue.forEach((element, index) => {
|
||||
this.handle(this.queue[index], true);
|
||||
this.queue.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
handle(packet, queue = false) {
|
||||
if (packet.op === OPCodes.HEARTBEAT_ACK) {
|
||||
this.ws.client._pong(this.ws.client._pingTimestamp);
|
||||
this.ws.lastHeartbeatAck = true;
|
||||
this.ws.client.emit('debug', 'Heartbeat acknowledged');
|
||||
} else if (packet.op === OPCodes.HEARTBEAT) {
|
||||
this.client.ws.send({
|
||||
op: OPCodes.HEARTBEAT,
|
||||
d: this.client.ws.sequence,
|
||||
});
|
||||
this.ws.client.emit('debug', 'Received gateway heartbeat');
|
||||
}
|
||||
|
||||
if (this.ws.status === Status.RECONNECTING) {
|
||||
this.ws.reconnecting = false;
|
||||
this.ws.checkIfReady();
|
||||
}
|
||||
|
||||
this.ws.setSequence(packet.s);
|
||||
|
||||
if (this.ws.disabledEvents[packet.t] !== undefined) return false;
|
||||
|
||||
if (this.ws.status !== Status.READY) {
|
||||
if (BeforeReadyWhitelist.indexOf(packet.t) === -1) {
|
||||
this.queue.push(packet);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!queue && this.queue.length > 0) this.handleQueue();
|
||||
if (this.handlers[packet.t]) return this.handlers[packet.t].handle(packet);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebSocketPacketManager;
|
||||
@@ -1,11 +0,0 @@
|
||||
class AbstractHandler {
|
||||
constructor(packetManager) {
|
||||
this.packetManager = packetManager;
|
||||
}
|
||||
|
||||
handle(packet) {
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AbstractHandler;
|
||||
@@ -1,15 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class ChannelCreateHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
this.packetManager.client.actions.ChannelCreate.handle(packet.d);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ChannelCreateHandler;
|
||||
|
||||
/**
|
||||
* Emitted whenever a channel is created.
|
||||
* @event Client#channelCreate
|
||||
* @param {DMChannel|GroupDMChannel|GuildChannel} channel The channel that was created
|
||||
*/
|
||||
@@ -1,9 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class ChannelDeleteHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
this.packetManager.client.actions.ChannelDelete.handle(packet.d);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ChannelDeleteHandler;
|
||||
@@ -1,37 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events } = require('../../../../util/Constants');
|
||||
|
||||
/*
|
||||
{ t: 'CHANNEL_PINS_UPDATE',
|
||||
s: 666,
|
||||
op: 0,
|
||||
d:
|
||||
{ last_pin_timestamp: '2016-08-28T17:37:13.171774+00:00',
|
||||
channel_id: '314866471639044027' } }
|
||||
*/
|
||||
|
||||
class ChannelPinsUpdate extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
const channel = client.channels.get(data.channel_id);
|
||||
const time = new Date(data.last_pin_timestamp);
|
||||
if (channel && time) {
|
||||
// Discord sends null for last_pin_timestamp if the last pinned message was removed
|
||||
channel.lastPinTimestamp = time.getTime() || null;
|
||||
|
||||
client.emit(Events.CHANNEL_PINS_UPDATE, channel, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ChannelPinsUpdate;
|
||||
|
||||
/**
|
||||
* Emitted whenever the pins of a channel are updated. Due to the nature of the WebSocket event, not much information
|
||||
* can be provided easily here - you need to manually check the pins yourself.
|
||||
* <warn>The `time` parameter will be a Unix Epoch Date object when there are no pins left.</warn>
|
||||
* @event Client#channelPinsUpdate
|
||||
* @param {DMChannel|GroupDMChannel|TextChannel} channel The channel that the pins update occurred in
|
||||
* @param {Date} time The time when the last pinned message was pinned
|
||||
*/
|
||||
@@ -1,20 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events } = require('../../../../util/Constants');
|
||||
|
||||
class ChannelUpdateHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const { old, updated } = this.packetManager.client.actions.ChannelUpdate.handle(packet.d);
|
||||
if (old && updated) {
|
||||
this.packetManager.client.emit(Events.CHANNEL_UPDATE, old, updated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ChannelUpdateHandler;
|
||||
|
||||
/**
|
||||
* Emitted whenever a channel is updated - e.g. name change, topic change.
|
||||
* @event Client#channelUpdate
|
||||
* @param {DMChannel|GroupDMChannel|GuildChannel} oldChannel The channel before the update
|
||||
* @param {DMChannel|GroupDMChannel|GuildChannel} newChannel The channel after the update
|
||||
*/
|
||||
@@ -1,23 +0,0 @@
|
||||
// ##untested handler##
|
||||
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events } = require('../../../../util/Constants');
|
||||
|
||||
class GuildBanAddHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
const user = client.users.add(data.user);
|
||||
if (guild && user) client.emit(Events.GUILD_BAN_ADD, guild, user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a member is banned from a guild.
|
||||
* @event Client#guildBanAdd
|
||||
* @param {Guild} guild The guild that the ban occurred in
|
||||
* @param {User} user The user that was banned
|
||||
*/
|
||||
|
||||
module.exports = GuildBanAddHandler;
|
||||
@@ -1,20 +0,0 @@
|
||||
// ##untested handler##
|
||||
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class GuildBanRemoveHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
client.actions.GuildBanRemove.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a member is unbanned from a guild.
|
||||
* @event Client#guildBanRemove
|
||||
* @param {Guild} guild The guild that the unban occurred in
|
||||
* @param {User} user The user that was unbanned
|
||||
*/
|
||||
|
||||
module.exports = GuildBanRemoveHandler;
|
||||
@@ -1,33 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events, Status } = require('../../../../util/Constants');
|
||||
|
||||
class GuildCreateHandler extends AbstractHandler {
|
||||
async handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
|
||||
let guild = client.guilds.get(data.id);
|
||||
if (guild) {
|
||||
if (!guild.available && !data.unavailable) {
|
||||
// A newly available guild
|
||||
guild._patch(data);
|
||||
this.packetManager.ws.checkIfReady();
|
||||
}
|
||||
} else {
|
||||
// A new guild
|
||||
guild = client.guilds.add(data);
|
||||
const emitEvent = client.ws.connection.status === Status.READY;
|
||||
if (emitEvent) {
|
||||
/**
|
||||
* Emitted whenever the client joins a guild.
|
||||
* @event Client#guildCreate
|
||||
* @param {Guild} guild The created guild
|
||||
*/
|
||||
if (client.options.fetchAllMembers) await guild.members.fetch();
|
||||
client.emit(Events.GUILD_CREATE, guild);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildCreateHandler;
|
||||
@@ -1,16 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class GuildDeleteHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
client.actions.GuildDelete.handle(packet.d);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a guild kicks the client or the guild is deleted/left.
|
||||
* @event Client#guildDelete
|
||||
* @param {Guild} guild The guild that was deleted
|
||||
*/
|
||||
|
||||
module.exports = GuildDeleteHandler;
|
||||
@@ -1,11 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class GuildEmojisUpdate extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
client.actions.GuildEmojisUpdate.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildEmojisUpdate;
|
||||
@@ -1,19 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events } = require('../../../../util/Constants');
|
||||
|
||||
class GuildIntegrationsHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
if (guild) client.emit(Events.GUILD_INTEGRATIONS_UPDATE, guild);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildIntegrationsHandler;
|
||||
|
||||
/**
|
||||
* Emitted whenever a guild integration is updated
|
||||
* @event Client#guildIntegrationsUpdate
|
||||
* @param {Guild} guild The guild whose integrations were updated
|
||||
*/
|
||||
@@ -1,27 +0,0 @@
|
||||
// ##untested handler##
|
||||
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events, Status } = require('../../../../util/Constants');
|
||||
|
||||
class GuildMemberAddHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
if (guild) {
|
||||
guild.memberCount++;
|
||||
const member = guild.members.add(data);
|
||||
if (client.ws.connection.status === Status.READY) {
|
||||
client.emit(Events.GUILD_MEMBER_ADD, member);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildMemberAddHandler;
|
||||
|
||||
/**
|
||||
* Emitted whenever a user joins a guild.
|
||||
* @event Client#guildMemberAdd
|
||||
* @param {GuildMember} member The member that has joined a guild
|
||||
*/
|
||||
@@ -1,13 +0,0 @@
|
||||
// ##untested handler##
|
||||
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class GuildMemberRemoveHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
client.actions.GuildMemberRemove.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildMemberRemoveHandler;
|
||||
@@ -1,29 +0,0 @@
|
||||
// ##untested handler##
|
||||
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events, Status } = require('../../../../util/Constants');
|
||||
|
||||
class GuildMemberUpdateHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
if (guild) {
|
||||
const member = guild.members.get(data.user.id);
|
||||
if (member) {
|
||||
const old = member._update(data);
|
||||
if (client.ws.connection.status === Status.READY) {
|
||||
/**
|
||||
* Emitted whenever a guild member's details (e.g. role, nickname) are changed
|
||||
* @event Client#guildMemberUpdate
|
||||
* @param {GuildMember} oldMember The member before the update
|
||||
* @param {GuildMember} newMember The member after the update
|
||||
*/
|
||||
client.emit(Events.GUILD_MEMBER_UPDATE, old, member);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildMemberUpdateHandler;
|
||||
@@ -1,28 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events } = require('../../../../util/Constants');
|
||||
const Collection = require('../../../../util/Collection');
|
||||
|
||||
class GuildMembersChunkHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
if (!guild) return;
|
||||
const members = new Collection();
|
||||
|
||||
for (const member of data.members) members.set(member.user.id, guild.members.add(member));
|
||||
|
||||
client.emit(Events.GUILD_MEMBERS_CHUNK, members, guild);
|
||||
|
||||
client.ws.lastHeartbeatAck = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a chunk of guild members is received (all members come from the same guild).
|
||||
* @event Client#guildMembersChunk
|
||||
* @param {Collection<Snowflake, GuildMember>} members The members in the chunk
|
||||
* @param {Guild} guild The guild related to the member chunk
|
||||
*/
|
||||
|
||||
module.exports = GuildMembersChunkHandler;
|
||||
@@ -1,11 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class GuildRoleCreateHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
client.actions.GuildRoleCreate.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildRoleCreateHandler;
|
||||
@@ -1,11 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class GuildRoleDeleteHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
client.actions.GuildRoleDelete.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildRoleDeleteHandler;
|
||||
@@ -1,11 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class GuildRoleUpdateHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
client.actions.GuildRoleUpdate.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildRoleUpdateHandler;
|
||||
@@ -1,11 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class GuildUpdateHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
client.actions.GuildUpdate.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildUpdateHandler;
|
||||
@@ -1,9 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class MessageCreateHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
this.packetManager.client.actions.MessageCreate.handle(packet.d);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageCreateHandler;
|
||||
@@ -1,9 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class MessageDeleteHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
this.packetManager.client.actions.MessageDelete.handle(packet.d);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageDeleteHandler;
|
||||
@@ -1,9 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class MessageDeleteBulkHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
this.packetManager.client.actions.MessageDeleteBulk.handle(packet.d);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageDeleteBulkHandler;
|
||||
@@ -1,13 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events } = require('../../../../util/Constants');
|
||||
|
||||
class MessageReactionAddHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
const { user, reaction } = client.actions.MessageReactionAdd.handle(data);
|
||||
if (reaction) client.emit(Events.MESSAGE_REACTION_ADD, reaction, user);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageReactionAddHandler;
|
||||
@@ -1,11 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class MessageReactionRemove extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
client.actions.MessageReactionRemove.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageReactionRemove;
|
||||
@@ -1,11 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class MessageReactionRemoveAll extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
client.actions.MessageReactionRemoveAll.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageReactionRemoveAll;
|
||||
@@ -1,20 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events } = require('../../../../util/Constants');
|
||||
|
||||
class MessageUpdateHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const { old, updated } = this.packetManager.client.actions.MessageUpdate.handle(packet.d);
|
||||
if (old && updated) {
|
||||
this.packetManager.client.emit(Events.MESSAGE_UPDATE, old, updated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageUpdateHandler;
|
||||
|
||||
/**
|
||||
* Emitted whenever a message is updated - e.g. embed or content change.
|
||||
* @event Client#messageUpdate
|
||||
* @param {Message} oldMessage The message before the update
|
||||
* @param {Message} newMessage The message after the update
|
||||
*/
|
||||
@@ -1,68 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events } = require('../../../../util/Constants');
|
||||
|
||||
class PresenceUpdateHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
let user = client.users.get(data.user.id);
|
||||
const guild = client.guilds.get(data.guild_id);
|
||||
|
||||
// Step 1
|
||||
if (!user) {
|
||||
if (data.user.username) {
|
||||
user = client.users.add(data.user);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const oldUser = user._update(data.user);
|
||||
if (!user.equals(oldUser)) {
|
||||
client.emit(Events.USER_UPDATE, oldUser, user);
|
||||
}
|
||||
|
||||
if (guild) {
|
||||
let oldPresence = guild.presences.get(user.id);
|
||||
if (oldPresence) oldPresence = oldPresence._clone();
|
||||
let member = guild.members.get(user.id);
|
||||
if (!member && data.status !== 'offline') {
|
||||
member = guild.members.add({
|
||||
user,
|
||||
roles: data.roles,
|
||||
deaf: false,
|
||||
mute: false,
|
||||
});
|
||||
client.emit(Events.GUILD_MEMBER_AVAILABLE, member);
|
||||
}
|
||||
guild.presences.add(Object.assign(data, { guild }));
|
||||
if (member && client.listenerCount(Events.PRESENCE_UPDATE)) {
|
||||
client.emit(Events.PRESENCE_UPDATE, oldPresence, member.presence);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a guild member's presence (e.g. status, activity) is changed.
|
||||
* @event Client#presenceUpdate
|
||||
* @param {?Presence} oldPresence The presence before the update, if one at all
|
||||
* @param {Presence} newPresence The presence after the update
|
||||
*/
|
||||
|
||||
/**
|
||||
* Emitted whenever a user's details (e.g. username, avatar) are changed.
|
||||
* <info>Disabling {@link Client#presenceUpdate} will cause this event to only fire
|
||||
* on {@link ClientUser} update.</info>
|
||||
* @event Client#userUpdate
|
||||
* @param {User} oldUser The user before the update
|
||||
* @param {User} newUser The user after the update
|
||||
*/
|
||||
|
||||
/**
|
||||
* Emitted whenever a member becomes available in a large guild.
|
||||
* @event Client#guildMemberAvailable
|
||||
* @param {GuildMember} member The member that became available
|
||||
*/
|
||||
|
||||
module.exports = PresenceUpdateHandler;
|
||||
@@ -1,41 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events } = require('../../../../util/Constants');
|
||||
let ClientUser;
|
||||
|
||||
class ReadyHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
|
||||
client.ws.heartbeat();
|
||||
|
||||
client.presence.userID = data.user.id;
|
||||
if (!ClientUser) ClientUser = require('../../../../structures/ClientUser');
|
||||
const clientUser = new ClientUser(client, data.user);
|
||||
client.user = clientUser;
|
||||
client.readyAt = new Date();
|
||||
client.users.set(clientUser.id, clientUser);
|
||||
|
||||
for (const guild of data.guilds) client.guilds.add(guild);
|
||||
|
||||
const t = client.setTimeout(() => {
|
||||
client.ws.connection.triggerReady();
|
||||
}, 1200 * data.guilds.length);
|
||||
|
||||
client.setMaxListeners(data.guilds.length + 10);
|
||||
|
||||
client.once('ready', () => {
|
||||
client.setMaxListeners(10);
|
||||
client.clearTimeout(t);
|
||||
});
|
||||
|
||||
const ws = this.packetManager.ws;
|
||||
|
||||
ws.sessionID = data.session_id;
|
||||
ws._trace = data._trace;
|
||||
client.emit(Events.DEBUG, `READY ${ws._trace.join(' -> ')} ${ws.sessionID}`);
|
||||
ws.checkIfReady();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ReadyHandler;
|
||||
@@ -1,28 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events, Status } = require('../../../../util/Constants');
|
||||
|
||||
class ResumedHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const ws = client.ws.connection;
|
||||
|
||||
ws._trace = packet.d._trace;
|
||||
|
||||
ws.status = Status.READY;
|
||||
this.packetManager.handleQueue();
|
||||
|
||||
const replayed = ws.sequence - ws.closeSequence;
|
||||
|
||||
ws.debug(`RESUMED ${ws._trace.join(' -> ')} | replayed ${replayed} events.`);
|
||||
client.emit(Events.RESUMED, replayed);
|
||||
ws.heartbeat();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a WebSocket resumes.
|
||||
* @event Client#resumed
|
||||
* @param {number} replayed The number of events that were replayed
|
||||
*/
|
||||
|
||||
module.exports = ResumedHandler;
|
||||
@@ -1,68 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events } = require('../../../../util/Constants');
|
||||
|
||||
class TypingStartHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
const channel = client.channels.get(data.channel_id);
|
||||
const user = client.users.get(data.user_id);
|
||||
const timestamp = new Date(data.timestamp * 1000);
|
||||
|
||||
if (channel && user) {
|
||||
if (channel.type === 'voice') {
|
||||
client.emit(Events.WARN, `Discord sent a typing packet to voice channel ${channel.id}`);
|
||||
return;
|
||||
}
|
||||
if (channel._typing.has(user.id)) {
|
||||
const typing = channel._typing.get(user.id);
|
||||
typing.lastTimestamp = timestamp;
|
||||
typing.resetTimeout(tooLate(channel, user));
|
||||
} else {
|
||||
channel._typing.set(user.id, new TypingData(client, timestamp, timestamp, tooLate(channel, user)));
|
||||
client.emit(Events.TYPING_START, channel, user);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TypingData {
|
||||
constructor(client, since, lastTimestamp, _timeout) {
|
||||
this.client = client;
|
||||
this.since = since;
|
||||
this.lastTimestamp = lastTimestamp;
|
||||
this._timeout = _timeout;
|
||||
}
|
||||
|
||||
resetTimeout(_timeout) {
|
||||
this.client.clearTimeout(this._timeout);
|
||||
this._timeout = _timeout;
|
||||
}
|
||||
|
||||
get elapsedTime() {
|
||||
return Date.now() - this.since;
|
||||
}
|
||||
}
|
||||
|
||||
function tooLate(channel, user) {
|
||||
return channel.client.setTimeout(() => {
|
||||
channel.client.emit(Events.TYPING_STOP, channel, user, channel._typing.get(user.id));
|
||||
channel._typing.delete(user.id);
|
||||
}, 6000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a user starts typing in a channel.
|
||||
* @event Client#typingStart
|
||||
* @param {Channel} channel The channel the user started typing in
|
||||
* @param {User} user The user that started typing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Emitted whenever a user stops typing in a channel.
|
||||
* @event Client#typingStop
|
||||
* @param {Channel} channel The channel the user stopped typing in
|
||||
* @param {User} user The user that stopped typing
|
||||
*/
|
||||
|
||||
module.exports = TypingStartHandler;
|
||||
@@ -1,11 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
class UserUpdateHandler extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
client.actions.UserUpdate.handle(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserUpdateHandler;
|
||||
@@ -1,19 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
|
||||
/*
|
||||
{
|
||||
"token": "my_token",
|
||||
"guild_id": "41771983423143937",
|
||||
"endpoint": "smart.loyal.discord.gg"
|
||||
}
|
||||
*/
|
||||
|
||||
class VoiceServerUpdate extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
client.emit('self.voiceServer', data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VoiceServerUpdate;
|
||||
@@ -6,6 +6,7 @@ const Messages = {
|
||||
TOKEN_INVALID: 'An invalid token was provided.',
|
||||
TOKEN_MISSING: 'Request to use token, but token was unavailable to the client.',
|
||||
|
||||
WS_CLOSE_REQUESTED: 'WebSocket closed due to user request.',
|
||||
WS_CONNECTION_TIMEOUT: 'The connection to the gateway timed out.',
|
||||
WS_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.',
|
||||
WS_NOT_OPEN: (data = 'data') => `Websocket not open to send ${data}`,
|
||||
|
||||
@@ -29,7 +29,7 @@ class Shard extends EventEmitter {
|
||||
this.manager = manager;
|
||||
|
||||
/**
|
||||
* ID of the shard
|
||||
* ID of the shard in the manager
|
||||
* @type {number}
|
||||
*/
|
||||
this.id = id;
|
||||
@@ -51,8 +51,10 @@ class Shard extends EventEmitter {
|
||||
* @type {Object}
|
||||
*/
|
||||
this.env = Object.assign({}, process.env, {
|
||||
SHARD_ID: this.id,
|
||||
SHARD_COUNT: this.manager.totalShards,
|
||||
SHARDING_MANAGER: true,
|
||||
SHARDS: this.id,
|
||||
TOTAL_SHARD_COUNT: this.manager.totalShards,
|
||||
DISCORD_TOKEN: this.manager.token,
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,7 +49,7 @@ class ShardClientUtil {
|
||||
* @readonly
|
||||
*/
|
||||
get id() {
|
||||
return this.client.options.shardId;
|
||||
return this.client.options.shards;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,8 @@ class ShardingManager extends EventEmitter {
|
||||
/**
|
||||
* @param {string} file Path to your shard script file
|
||||
* @param {Object} [options] Options for the sharding manager
|
||||
* @param {number|string} [options.totalShards='auto'] Number of shards to spawn, or "auto"
|
||||
* @param {string|number[]} [options.totalShards='auto'] Number of total shards of all shard managers or "auto"
|
||||
* @param {string|number[]} [options.shardList='auto'] List of shards to spawn or "auto"
|
||||
* @param {ShardingManagerMode} [options.mode='process'] Which mode to use for shards
|
||||
* @param {boolean} [options.respawn=true] Whether shards should automatically respawn upon exiting
|
||||
* @param {string[]} [options.shardArgs=[]] Arguments to pass to the shard script when spawning
|
||||
@@ -58,16 +59,33 @@ class ShardingManager extends EventEmitter {
|
||||
if (!stats.isFile()) throw new Error('CLIENT_INVALID_OPTION', 'File', 'a file');
|
||||
|
||||
/**
|
||||
* Amount of shards that this manager is going to spawn
|
||||
* @type {number|string}
|
||||
* List of shards this sharding manager spawns
|
||||
* @type {string|number[]}
|
||||
*/
|
||||
this.totalShards = options.totalShards;
|
||||
this.shardList = options.shardList || 'auto';
|
||||
if (this.shardList !== 'auto') {
|
||||
if (!Array.isArray(this.shardList)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'shardList', 'an array.');
|
||||
}
|
||||
this.shardList = [...new Set(this.shardList)];
|
||||
if (this.shardList.length < 1) throw new RangeError('CLIENT_INVALID_OPTION', 'shardList', 'at least 1 ID.');
|
||||
if (this.shardList.some(shardID => typeof shardID !== 'number' || isNaN(shardID) ||
|
||||
!Number.isInteger(shardID) || shardID < 0)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'shardList', 'an array of postive integers.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Amount of shards that all sharding managers spawn in total
|
||||
* @type {number}
|
||||
*/
|
||||
this.totalShards = options.totalShards || 'auto';
|
||||
if (this.totalShards !== 'auto') {
|
||||
if (typeof this.totalShards !== 'number' || isNaN(this.totalShards)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'a number.');
|
||||
}
|
||||
if (this.totalShards < 1) throw new RangeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'at least 1.');
|
||||
if (this.totalShards !== Math.floor(this.totalShards)) {
|
||||
if (!Number.isInteger(this.totalShards)) {
|
||||
throw new RangeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'an integer.');
|
||||
}
|
||||
}
|
||||
@@ -150,21 +168,31 @@ class ShardingManager extends EventEmitter {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'a number.');
|
||||
}
|
||||
if (amount < 1) throw new RangeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'at least 1.');
|
||||
if (amount !== Math.floor(amount)) {
|
||||
if (!Number.isInteger(amount)) {
|
||||
throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'an integer.');
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure this many shards haven't already been spawned
|
||||
if (this.shards.size >= amount) throw new Error('SHARDING_ALREADY_SPAWNED', this.shards.size);
|
||||
this.totalShards = amount;
|
||||
if (this.shardList === 'auto' || this.totalShards === 'auto' || this.totalShards !== amount) {
|
||||
this.shardList = [...Array(amount).keys()];
|
||||
}
|
||||
if (this.totalShards === 'auto' || this.totalShards !== amount) {
|
||||
this.totalShards = amount;
|
||||
}
|
||||
|
||||
if (this.shardList.some(shardID => shardID >= amount)) {
|
||||
throw new RangeError('CLIENT_INVALID_OPTION', 'Amount of shards',
|
||||
'bigger than the highest shardID in the shardList option.');
|
||||
}
|
||||
|
||||
// Spawn the shards
|
||||
for (let s = 1; s <= amount; s++) {
|
||||
for (const shardID of this.shardList) {
|
||||
const promises = [];
|
||||
const shard = this.createShard();
|
||||
const shard = this.createShard(shardID);
|
||||
promises.push(shard.spawn(waitForReady));
|
||||
if (delay > 0 && s !== amount) promises.push(Util.delayFor(delay));
|
||||
if (delay > 0 && this.shards.size !== this.shardList.length - 1) promises.push(Util.delayFor(delay));
|
||||
await Promise.all(promises); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ class GuildMemberStore extends DataStore {
|
||||
resolve(query || limit ? new Collection() : this);
|
||||
return;
|
||||
}
|
||||
this.guild.client.ws.send({
|
||||
this.guild.shard.send({
|
||||
op: OPCodes.REQUEST_GUILD_MEMBERS,
|
||||
d: {
|
||||
guild_id: this.guild.id,
|
||||
|
||||
@@ -11,7 +11,15 @@ class ClientPresence extends Presence {
|
||||
async set(presence) {
|
||||
const packet = await this._parse(presence);
|
||||
this.patch(packet);
|
||||
this.client.ws.send({ op: OPCodes.STATUS_UPDATE, d: packet });
|
||||
if (typeof presence.shardID === 'undefined') {
|
||||
this.client.ws.broadcast({ op: OPCodes.STATUS_UPDATE, d: packet });
|
||||
} else if (Array.isArray(presence.shardID)) {
|
||||
for (const shardID of presence.shardID) {
|
||||
this.client.ws.shards[shardID].send({ op: OPCodes.STATUS_UPDATE, d: packet });
|
||||
}
|
||||
} else {
|
||||
this.client.ws.shards[presence.shardID].send({ op: OPCodes.STATUS_UPDATE, d: packet });
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@ class ClientUser extends Structures.get('User') {
|
||||
*/
|
||||
this.verified = data.verified;
|
||||
|
||||
this._typing = new Map();
|
||||
|
||||
/**
|
||||
* If the bot's {@link ClientApplication#owner Owner} has MFA enabled on their account
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.mfaEnabled = typeof data.mfa_enabled === 'boolean' ? data.mfa_enabled : null;
|
||||
|
||||
this._typing = new Map();
|
||||
|
||||
if (data.token) this.client.token = data.token;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ class ClientUser extends Structures.get('User') {
|
||||
return this.client.api.users('@me').patch({ data })
|
||||
.then(newData => {
|
||||
this.client.token = newData.token;
|
||||
return this.client.actions.UserUpdate.handle(newData).updated;
|
||||
const { updated } = this.client.actions.UserUpdate.handle(newData);
|
||||
if (updated) return updated;
|
||||
return this;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -84,6 +86,7 @@ class ClientUser extends Structures.get('User') {
|
||||
* @property {string} [activity.name] Name of the activity
|
||||
* @property {ActivityType|number} [activity.type] Type of the activity
|
||||
* @property {string} [activity.url] Stream url
|
||||
* @property {?number|number[]} [shardID] Shard Id(s) to have the activity set on
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -112,6 +115,7 @@ class ClientUser extends Structures.get('User') {
|
||||
/**
|
||||
* Sets the status of the client user.
|
||||
* @param {PresenceStatus} status Status to change to
|
||||
* @param {?number|number[]} [shardID] Shard ID(s) to have the activity set on
|
||||
* @returns {Promise<Presence>}
|
||||
* @example
|
||||
* // Set the client user's status
|
||||
@@ -119,8 +123,8 @@ class ClientUser extends Structures.get('User') {
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setStatus(status) {
|
||||
return this.setPresence({ status });
|
||||
setStatus(status, shardID) {
|
||||
return this.setPresence({ status, shardID });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,6 +133,7 @@ class ClientUser extends Structures.get('User') {
|
||||
* @type {Object}
|
||||
* @property {string} [url] Twitch stream URL
|
||||
* @property {ActivityType|number} [type] Type of the activity
|
||||
* @property {?number|number[]} [shardID] Shard Id(s) to have the activity set on
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -143,10 +148,10 @@ class ClientUser extends Structures.get('User') {
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setActivity(name, options = {}) {
|
||||
if (!name) return this.setPresence({ activity: null });
|
||||
if (!name) return this.setPresence({ activity: null, shardID: options.shardID });
|
||||
|
||||
const activity = Object.assign({}, options, typeof name === 'object' ? name : { name });
|
||||
return this.setPresence({ activity });
|
||||
return this.setPresence({ activity, shardID: activity.shardID });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -74,6 +74,21 @@ class Guild extends Base {
|
||||
this._patch(data);
|
||||
if (!data.channels) this.available = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The id of the shard this Guild belongs to.
|
||||
* @type {number}
|
||||
*/
|
||||
this.shardID = data.shardID;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Shard this Guild belongs to.
|
||||
* @type {WebSocketShard}
|
||||
* @readonly
|
||||
*/
|
||||
get shard() {
|
||||
return this.client.ws.shards[this.shardID];
|
||||
}
|
||||
|
||||
/* eslint-disable complexity */
|
||||
|
||||
@@ -5,8 +5,10 @@ const browser = exports.browser = typeof window !== 'undefined';
|
||||
/**
|
||||
* Options for a client.
|
||||
* @typedef {Object} ClientOptions
|
||||
* @property {number} [shardId=0] ID of the shard to run
|
||||
* @property {number} [shardCount=0] Total number of shards
|
||||
* @property {number|number[]} [shards=0] ID of the shard to run, or an array of shard IDs
|
||||
* @property {number} [shardCount=1] Total number of shards that will be spawned by this Client
|
||||
* @property {number} [totalShardCount=1] The total amount of shards used by all processes of this bot
|
||||
* (e.g. recommended shard count, shard count of the ShardingManager)
|
||||
* @property {number} [messageCacheMaxSize=200] Maximum number of messages to cache per channel
|
||||
* (-1 or Infinity for unlimited - don't do this without message sweeping, otherwise memory usage will climb
|
||||
* indefinitely)
|
||||
@@ -33,9 +35,9 @@ const browser = exports.browser = typeof window !== 'undefined';
|
||||
* @property {HTTPOptions} [http] HTTP options
|
||||
*/
|
||||
exports.DefaultOptions = {
|
||||
shardId: 0,
|
||||
shardCount: 0,
|
||||
internalSharding: false,
|
||||
shards: 0,
|
||||
shardCount: 1,
|
||||
totalShardCount: 1,
|
||||
messageCacheMaxSize: 200,
|
||||
messageCacheLifetime: 0,
|
||||
messageSweepInterval: 0,
|
||||
@@ -86,10 +88,10 @@ exports.UserAgent = browser ? null :
|
||||
`DiscordBot (${Package.homepage.split('#')[0]}, ${Package.version}) Node.js/${process.version}`;
|
||||
|
||||
exports.WSCodes = {
|
||||
1000: 'Connection gracefully closed',
|
||||
4004: 'Tried to identify with an invalid token',
|
||||
4010: 'Sharding data provided was invalid',
|
||||
4011: 'Shard would be on too many guilds if connected',
|
||||
1000: 'WS_CLOSE_REQUESTED',
|
||||
4004: 'TOKEN_INVALID',
|
||||
4010: 'SHARDING_INVALID',
|
||||
4011: 'SHARDING_REQUIRED',
|
||||
};
|
||||
|
||||
const AllowedImageFormats = [
|
||||
@@ -253,6 +255,9 @@ exports.Events = {
|
||||
ERROR: 'error',
|
||||
WARN: 'warn',
|
||||
DEBUG: 'debug',
|
||||
SHARD_READY: 'shardReady',
|
||||
INVALIDATED: 'invalidated',
|
||||
RAW: 'raw',
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@ const Discord = require('../');
|
||||
const { token } = require('./auth.json');
|
||||
|
||||
const client = new Discord.Client({
|
||||
shardId: process.argv[2],
|
||||
shardID: process.argv[2],
|
||||
shardCount: process.argv[3],
|
||||
});
|
||||
|
||||
@@ -20,8 +20,8 @@ client.on('message', msg => {
|
||||
process.send(123);
|
||||
|
||||
client.on('ready', () => {
|
||||
console.log('Ready', client.options.shardId);
|
||||
if (client.options.shardId === 0)
|
||||
console.log('Ready', client.options.shardID);
|
||||
if (client.options.shardID === 0)
|
||||
setTimeout(() => {
|
||||
console.log('kek dying');
|
||||
client.destroy();
|
||||
|
||||
@@ -4,7 +4,9 @@ const { token, prefix, owner } = require('./auth.js');
|
||||
// eslint-disable-next-line no-console
|
||||
const log = (...args) => console.log(process.uptime().toFixed(3), ...args);
|
||||
|
||||
const client = new Discord.Client();
|
||||
const client = new Discord.Client({
|
||||
shardCount: 2,
|
||||
});
|
||||
|
||||
client.on('debug', log);
|
||||
client.on('ready', () => {
|
||||
|
||||
81
typings/index.d.ts
vendored
81
typings/index.d.ts
vendored
@@ -80,17 +80,20 @@ declare module 'discord.js' {
|
||||
|
||||
export class BaseClient extends EventEmitter {
|
||||
constructor(options?: ClientOptions);
|
||||
private _intervals: Set<NodeJS.Timer>;
|
||||
private _timeouts: Set<NodeJS.Timer>;
|
||||
private _intervals: Set<NodeJS.Timer>;
|
||||
private _immediates: Set<NodeJS.Immediate>;
|
||||
private readonly api: object;
|
||||
private rest: object;
|
||||
|
||||
public options: ClientOptions;
|
||||
public clearInterval(interval: NodeJS.Timer): void;
|
||||
public clearTimeout(timeout: NodeJS.Timer): void;
|
||||
public clearImmediate(timeout: NodeJS.Immediate): void;
|
||||
public destroy(): void;
|
||||
public setInterval(fn: Function, delay: number, ...args: any[]): NodeJS.Timer;
|
||||
public setTimeout(fn: Function, delay: number, ...args: any[]): NodeJS.Timer;
|
||||
public setImmediate(fn: Function, delay: number, ...args: any[]): NodeJS.Immediate;
|
||||
public toJSON(...props: { [key: string]: boolean | string }[]): object;
|
||||
}
|
||||
|
||||
@@ -133,31 +136,26 @@ declare module 'discord.js' {
|
||||
|
||||
export class Client extends BaseClient {
|
||||
constructor(options?: ClientOptions);
|
||||
private readonly _pingTimestamp: number;
|
||||
private actions: object;
|
||||
private manager: ClientManager;
|
||||
private voice: object;
|
||||
private ws: object;
|
||||
private _eval(script: string): any;
|
||||
private _pong(startTime: number): void;
|
||||
private _validateOptions(options?: ClientOptions): void;
|
||||
|
||||
public broadcasts: VoiceBroadcast[];
|
||||
public channels: ChannelStore;
|
||||
public readonly emojis: GuildEmojiStore;
|
||||
public guilds: GuildStore;
|
||||
public readonly ping: number;
|
||||
public pings: number[];
|
||||
public readyAt: Date;
|
||||
public readyAt: Date | null;
|
||||
public readonly readyTimestamp: number;
|
||||
public shard: ShardClientUtil;
|
||||
public readonly status: Status;
|
||||
public token: string;
|
||||
public readonly uptime: number;
|
||||
public user: ClientUser;
|
||||
public user: ClientUser | null;
|
||||
public users: UserStore;
|
||||
public readonly voiceConnections: Collection<Snowflake, VoiceConnection>;
|
||||
public ws: WebSocketManager;
|
||||
public createVoiceBroadcast(): VoiceBroadcast;
|
||||
public destroy(): void;
|
||||
public fetchApplication(): Promise<ClientApplication>;
|
||||
public fetchInvite(invite: InviteResolvable): Promise<Invite>;
|
||||
public fetchVoiceRegions(): Promise<Collection<string, VoiceRegion>>;
|
||||
@@ -171,7 +169,7 @@ declare module 'discord.js' {
|
||||
public on(event: 'channelPinsUpdate', listener: (channel: Channel, time: Date) => void): this;
|
||||
public on(event: 'channelUpdate', listener: (oldChannel: Channel, newChannel: Channel) => void): this;
|
||||
public on(event: 'debug' | 'warn', listener: (info: string) => void): this;
|
||||
public on(event: 'disconnect', listener: (event: any) => void): this;
|
||||
public on(event: 'disconnect', listener: (event: any, shardID: number) => void): this;
|
||||
public on(event: 'emojiCreate' | 'emojiDelete', listener: (emoji: GuildEmoji) => void): this;
|
||||
public on(event: 'emojiUpdate', listener: (oldEmoji: GuildEmoji, newEmoji: GuildEmoji) => void): this;
|
||||
public on(event: 'error', listener: (error: Error) => void): this;
|
||||
@@ -189,10 +187,12 @@ declare module 'discord.js' {
|
||||
public on(event: 'messageUpdate', listener: (oldMessage: Message, newMessage: Message) => void): this;
|
||||
public on(event: 'presenceUpdate', listener: (oldPresence: Presence | undefined, newPresence: Presence) => void): this;
|
||||
public on(event: 'rateLimit', listener: (rateLimitData: RateLimitData) => void): this;
|
||||
public on(event: 'ready' | 'reconnecting', listener: () => void): this;
|
||||
public on(event: 'resumed', listener: (replayed: number) => void): this;
|
||||
public on(event: 'ready', listener: () => void): this;
|
||||
public on(event: 'reconnecting', listener: (shardID: number) => void): this;
|
||||
public on(event: 'resumed', listener: (replayed: number, shardID: number) => void): this;
|
||||
public on(event: 'roleCreate' | 'roleDelete', listener: (role: Role) => void): this;
|
||||
public on(event: 'roleUpdate', listener: (oldRole: Role, newRole: Role) => void): this;
|
||||
public on(event: 'shardReady', listener: (shardID: number) => void): this;
|
||||
public on(event: 'typingStart' | 'typingStop', listener: (channel: Channel, user: User) => void): this;
|
||||
public on(event: 'userUpdate', listener: (oldUser: User, newUser: User) => void): this;
|
||||
public on(event: 'voiceStateUpdate', listener: (oldState: VoiceState | undefined, newState: VoiceState) => void): this;
|
||||
@@ -203,7 +203,7 @@ declare module 'discord.js' {
|
||||
public once(event: 'channelPinsUpdate', listener: (channel: Channel, time: Date) => void): this;
|
||||
public once(event: 'channelUpdate', listener: (oldChannel: Channel, newChannel: Channel) => void): this;
|
||||
public once(event: 'debug' | 'warn', listener: (info: string) => void): this;
|
||||
public once(event: 'disconnect', listener: (event: any) => void): this;
|
||||
public once(event: 'disconnect', listener: (event: any, shardID: number) => void): this;
|
||||
public once(event: 'emojiCreate' | 'emojiDelete', listener: (emoji: GuildEmoji) => void): this;
|
||||
public once(event: 'emojiUpdate', listener: (oldEmoji: GuildEmoji, newEmoji: GuildEmoji) => void): this;
|
||||
public once(event: 'error', listener: (error: Error) => void): this;
|
||||
@@ -221,10 +221,12 @@ declare module 'discord.js' {
|
||||
public once(event: 'messageUpdate', listener: (oldMessage: Message, newMessage: Message) => void): this;
|
||||
public once(event: 'presenceUpdate', listener: (oldPresence: Presence | undefined, newPresence: Presence) => void): this;
|
||||
public once(event: 'rateLimit', listener: (rateLimitData: RateLimitData) => void): this;
|
||||
public once(event: 'ready' | 'reconnecting', listener: () => void): this;
|
||||
public once(event: 'resumed', listener: (replayed: number) => void): this;
|
||||
public once(event: 'ready', listener: () => void): this;
|
||||
public once(event: 'reconnecting', listener: (shardID: number) => void): this;
|
||||
public once(event: 'resumed', listener: (replayed: number, shardID: number) => void): this;
|
||||
public once(event: 'roleCreate' | 'roleDelete', listener: (role: Role) => void): this;
|
||||
public once(event: 'roleUpdate', listener: (oldRole: Role, newRole: Role) => void): this;
|
||||
public once(event: 'shardReady', listener: (shardID: number) => void): this;
|
||||
public once(event: 'typingStart' | 'typingStop', listener: (channel: Channel, user: User) => void): this;
|
||||
public once(event: 'userUpdate', listener: (oldUser: User, newUser: User) => void): this;
|
||||
public once(event: 'voiceStateUpdate', listener: (oldState: VoiceState | undefined, newState: VoiceState) => void): this;
|
||||
@@ -252,18 +254,11 @@ declare module 'discord.js' {
|
||||
public toString(): string;
|
||||
}
|
||||
|
||||
class ClientManager {
|
||||
constructor(client: Client);
|
||||
public client: Client;
|
||||
public heartbeatInterval: number;
|
||||
public readonly status: number;
|
||||
public connectToWebSocket(token: string, resolve: Function, reject: Function): void;
|
||||
}
|
||||
|
||||
export interface ActivityOptions {
|
||||
name?: string;
|
||||
url?: string;
|
||||
type?: ActivityType | number;
|
||||
shardID?: number | number[];
|
||||
}
|
||||
|
||||
export class ClientUser extends User {
|
||||
@@ -275,7 +270,7 @@ declare module 'discord.js' {
|
||||
public setAFK(afk: boolean): Promise<Presence>;
|
||||
public setAvatar(avatar: BufferResolvable | Base64Resolvable): Promise<ClientUser>;
|
||||
public setPresence(data: PresenceData): Promise<Presence>;
|
||||
public setStatus(status: PresenceStatus): Promise<Presence>;
|
||||
public setStatus(status: PresenceStatus, shardID?: number | number[]): Promise<Presence>;
|
||||
public setUsername(username: string): Promise<ClientUser>;
|
||||
}
|
||||
|
||||
@@ -1289,6 +1284,31 @@ declare module 'discord.js' {
|
||||
constructor(id: string, token: string, options?: ClientOptions);
|
||||
}
|
||||
|
||||
export class WebSocketManager {
|
||||
constructor(client: Client);
|
||||
public readonly client: Client;
|
||||
public gateway: string | undefined;
|
||||
public readonly ping: number;
|
||||
public shards: WebSocketShard[];
|
||||
public sessionStartLimit: { total: number; remaining: number; reset_after: number; };
|
||||
public status: Status;
|
||||
public broadcast(packet: any): void;
|
||||
}
|
||||
|
||||
export class WebSocketShard extends EventEmitter {
|
||||
constructor(manager: WebSocketManager, id: number, oldShard?: WebSocketShard);
|
||||
public id: number;
|
||||
public readonly ping: number;
|
||||
public pings: number[];
|
||||
public status: Status;
|
||||
public manager: WebSocketManager;
|
||||
public send(data: object): void;
|
||||
|
||||
public on(event: 'ready', listener: () => void): this;
|
||||
|
||||
public once(event: 'ready', listener: () => void): this;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Stores
|
||||
@@ -1572,9 +1592,9 @@ declare module 'discord.js' {
|
||||
};
|
||||
|
||||
type ClientOptions = {
|
||||
presence?: PresenceData;
|
||||
shardId?: number;
|
||||
shards?: number | number[];
|
||||
shardCount?: number;
|
||||
totalShardCount?: number;
|
||||
messageCacheMaxSize?: number;
|
||||
messageCacheLifetime?: number;
|
||||
messageSweepInterval?: number;
|
||||
@@ -1582,7 +1602,9 @@ declare module 'discord.js' {
|
||||
disableEveryone?: boolean;
|
||||
restWsBridgeTimeout?: number;
|
||||
restTimeOffset?: number;
|
||||
retryLimit?: number,
|
||||
restSweepInterval?: number;
|
||||
retryLimit?: number;
|
||||
presence?: PresenceData;
|
||||
disabledEvents?: WSEventType[];
|
||||
ws?: WebSocketOptions;
|
||||
http?: HTTPOptions;
|
||||
@@ -1982,7 +2004,8 @@ declare module 'discord.js' {
|
||||
name?: string;
|
||||
type?: ActivityType | number;
|
||||
url?: string;
|
||||
}
|
||||
};
|
||||
shardID?: number | number[];
|
||||
};
|
||||
|
||||
type PresenceResolvable = Presence | UserResolvable | Snowflake;
|
||||
|
||||
Reference in New Issue
Block a user