mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-14 18:43: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:
@@ -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;
|
||||
@@ -1,45 +0,0 @@
|
||||
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 guild = client.guilds.get(data.guild_id);
|
||||
if (guild) {
|
||||
// Update the state
|
||||
const oldState = guild.voiceStates.has(data.user_id) ?
|
||||
guild.voiceStates.get(data.user_id)._clone() :
|
||||
new VoiceState(guild, { user_id: data.user_id });
|
||||
|
||||
const newState = guild.voiceStates.add(data);
|
||||
|
||||
// Get the member
|
||||
let member = guild.members.get(data.user_id);
|
||||
if (member && data.member) {
|
||||
member._patch(data.member);
|
||||
} else if (data.member && data.member.user && data.member.joined_at) {
|
||||
member = guild.members.add(data.member);
|
||||
}
|
||||
|
||||
// Emit event
|
||||
if (member && member.user.id === client.user.id && data.channel_id) {
|
||||
client.emit('self.voiceStateUpdate', data);
|
||||
}
|
||||
|
||||
client.emit(Events.VOICE_STATE_UPDATE, oldState, newState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a member changes voice state - e.g. joins/leaves a channel, mutes/unmutes.
|
||||
* @event Client#voiceStateUpdate
|
||||
* @param {?VoiceState} oldState The voice state before the update
|
||||
* @param {VoiceState} newState The voice state after the update
|
||||
*/
|
||||
|
||||
module.exports = VoiceStateUpdateHandler;
|
||||
@@ -1,19 +0,0 @@
|
||||
const AbstractHandler = require('./AbstractHandler');
|
||||
const { Events } = require('../../../../util/Constants');
|
||||
|
||||
class WebhooksUpdate extends AbstractHandler {
|
||||
handle(packet) {
|
||||
const client = this.packetManager.client;
|
||||
const data = packet.d;
|
||||
const channel = client.channels.get(data.channel_id);
|
||||
if (channel) client.emit(Events.WEBHOOKS_UPDATE, channel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever a guild text channel has its webhooks changed.
|
||||
* @event Client#webhookUpdate
|
||||
* @param {TextChannel} channel The channel that had a webhook update
|
||||
*/
|
||||
|
||||
module.exports = WebhooksUpdate;
|
||||
Reference in New Issue
Block a user