Initial commit
This commit is contained in:
394
src/client/websocket/WebSocketManager.js
Normal file
394
src/client/websocket/WebSocketManager.js
Normal file
@@ -0,0 +1,394 @@
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('node:events');
|
||||
const { setImmediate } = require('node:timers');
|
||||
const { setTimeout: sleep } = require('node:timers/promises');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { GatewayCloseCodes, GatewayDispatchEvents, Routes } = require('discord-api-types/v9');
|
||||
const WebSocketShard = require('./WebSocketShard');
|
||||
const PacketHandlers = require('./handlers');
|
||||
const { Error } = require('../../errors');
|
||||
const Events = require('../../util/Events');
|
||||
const ShardEvents = require('../../util/ShardEvents');
|
||||
const Status = require('../../util/Status');
|
||||
|
||||
const BeforeReadyWhitelist = [
|
||||
GatewayDispatchEvents.Ready,
|
||||
GatewayDispatchEvents.Resumed,
|
||||
GatewayDispatchEvents.GuildCreate,
|
||||
GatewayDispatchEvents.GuildDelete,
|
||||
GatewayDispatchEvents.GuildMembersChunk,
|
||||
GatewayDispatchEvents.GuildMemberAdd,
|
||||
GatewayDispatchEvents.GuildMemberRemove,
|
||||
];
|
||||
|
||||
const UNRECOVERABLE_CLOSE_CODES = [
|
||||
GatewayCloseCodes.AuthenticationFailed,
|
||||
GatewayCloseCodes.InvalidShard,
|
||||
GatewayCloseCodes.ShardingRequired,
|
||||
GatewayCloseCodes.InvalidIntents,
|
||||
GatewayCloseCodes.DisallowedIntents,
|
||||
];
|
||||
const UNRESUMABLE_CLOSE_CODES = [1000, GatewayCloseCodes.AlreadyAuthenticated, GatewayCloseCodes.InvalidSeq];
|
||||
|
||||
/**
|
||||
* The WebSocket manager for this client.
|
||||
* <info>This class forwards raw dispatch events,
|
||||
* read more about it here {@link https://discord.com/developers/docs/topics/gateway}</info>
|
||||
* @extends EventEmitter
|
||||
*/
|
||||
class WebSocketManager extends EventEmitter {
|
||||
constructor(client) {
|
||||
super();
|
||||
|
||||
/**
|
||||
* The client that instantiated this WebSocketManager
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
* @name WebSocketManager#client
|
||||
*/
|
||||
Object.defineProperty(this, 'client', { value: client });
|
||||
|
||||
/**
|
||||
* The gateway this manager uses
|
||||
* @type {?string}
|
||||
*/
|
||||
this.gateway = null;
|
||||
|
||||
/**
|
||||
* The amount of shards this manager handles
|
||||
* @private
|
||||
* @type {number}
|
||||
*/
|
||||
this.totalShards = this.client.options.shards.length;
|
||||
|
||||
/**
|
||||
* A collection of all shards this manager handles
|
||||
* @type {Collection<number, WebSocketShard>}
|
||||
*/
|
||||
this.shards = new Collection();
|
||||
|
||||
/**
|
||||
* An array of shards to be connected or that need to reconnect
|
||||
* @type {Set<WebSocketShard>}
|
||||
* @private
|
||||
* @name WebSocketManager#shardQueue
|
||||
*/
|
||||
Object.defineProperty(this, 'shardQueue', { value: new Set(), writable: true });
|
||||
|
||||
/**
|
||||
* An array of queued events before this WebSocketManager became ready
|
||||
* @type {Object[]}
|
||||
* @private
|
||||
* @name WebSocketManager#packetQueue
|
||||
*/
|
||||
Object.defineProperty(this, 'packetQueue', { value: [] });
|
||||
|
||||
/**
|
||||
* The current status of this WebSocketManager
|
||||
* @type {Status}
|
||||
*/
|
||||
this.status = Status.Idle;
|
||||
|
||||
/**
|
||||
* If this manager was destroyed. It will prevent shards from reconnecting
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.destroyed = false;
|
||||
|
||||
/**
|
||||
* If this manager is currently reconnecting one or multiple shards
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.reconnecting = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The average ping of all WebSocketShards
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get ping() {
|
||||
const sum = this.shards.reduce((a, b) => a + b.ping, 0);
|
||||
return sum / this.shards.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a debug message.
|
||||
* @param {string} message The debug message
|
||||
* @param {?WebSocketShard} [shard] The shard that emitted this message, if any
|
||||
* @private
|
||||
*/
|
||||
debug(message, shard) {
|
||||
this.client.emit(Events.Debug, `[WS => ${shard ? `Shard ${shard.id}` : 'Manager'}] ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects this manager to the gateway.
|
||||
* @private
|
||||
*/
|
||||
async connect() {
|
||||
const invalidToken = new Error(GatewayCloseCodes[GatewayCloseCodes.AuthenticationFailed]);
|
||||
const {
|
||||
url: gatewayURL,
|
||||
shards: recommendedShards,
|
||||
session_start_limit: sessionStartLimit,
|
||||
} = await this.client.api.gateway.bot.get().catch(error => {
|
||||
throw error.httpStatus === 401 ? invalidToken : error;
|
||||
});
|
||||
|
||||
const { total, remaining } = sessionStartLimit;
|
||||
|
||||
this.debug(`Fetched Gateway Information
|
||||
URL: ${gatewayURL}
|
||||
Recommended Shards: ${recommendedShards}`);
|
||||
|
||||
this.debug(`Session Limit Information
|
||||
Total: ${total}
|
||||
Remaining: ${remaining}`);
|
||||
|
||||
this.gateway = `${gatewayURL}/`;
|
||||
|
||||
let { shards } = this.client.options;
|
||||
|
||||
if (shards === 'auto') {
|
||||
this.debug(`Using the recommended shard count provided by Discord: ${recommendedShards}`);
|
||||
this.totalShards = this.client.options.shardCount = recommendedShards;
|
||||
shards = this.client.options.shards = Array.from({ length: recommendedShards }, (_, i) => i);
|
||||
}
|
||||
|
||||
this.totalShards = shards.length;
|
||||
this.debug(`Spawning shards: ${shards.join(', ')}`);
|
||||
this.shardQueue = new Set(shards.map(id => new WebSocketShard(this, id)));
|
||||
|
||||
return this.createShards();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the creation of a shard.
|
||||
* @returns {Promise<boolean>}
|
||||
* @private
|
||||
*/
|
||||
async createShards() {
|
||||
// If we don't have any shards to handle, return
|
||||
if (!this.shardQueue.size) return false;
|
||||
|
||||
const [shard] = this.shardQueue;
|
||||
|
||||
this.shardQueue.delete(shard);
|
||||
|
||||
if (!shard.eventsAttached) {
|
||||
shard.on(ShardEvents.AllReady, unavailableGuilds => {
|
||||
/**
|
||||
* Emitted when a shard turns ready.
|
||||
* @event Client#shardReady
|
||||
* @param {number} id The shard id that turned ready
|
||||
* @param {?Set<Snowflake>} unavailableGuilds Set of unavailable guild ids, if any
|
||||
*/
|
||||
this.client.emit(Events.ShardReady, shard.id, unavailableGuilds);
|
||||
|
||||
if (!this.shardQueue.size) this.reconnecting = false;
|
||||
this.checkShardsReady();
|
||||
});
|
||||
|
||||
shard.on(ShardEvents.Close, event => {
|
||||
if (event.code === 1_000 ? this.destroyed : UNRECOVERABLE_CLOSE_CODES.includes(event.code)) {
|
||||
/**
|
||||
* Emitted when a shard's WebSocket disconnects and will no longer reconnect.
|
||||
* @event Client#shardDisconnect
|
||||
* @param {CloseEvent} event The WebSocket close event
|
||||
* @param {number} id The shard id that disconnected
|
||||
*/
|
||||
this.client.emit(Events.ShardDisconnect, event, shard.id);
|
||||
this.debug(GatewayCloseCodes[event.code], shard);
|
||||
return;
|
||||
}
|
||||
|
||||
if (UNRESUMABLE_CLOSE_CODES.includes(event.code)) {
|
||||
// These event codes cannot be resumed
|
||||
shard.sessionId = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted when a shard is attempting to reconnect or re-identify.
|
||||
* @event Client#shardReconnecting
|
||||
* @param {number} id The shard id that is attempting to reconnect
|
||||
*/
|
||||
this.client.emit(Events.ShardReconnecting, shard.id);
|
||||
|
||||
this.shardQueue.add(shard);
|
||||
|
||||
if (shard.sessionId) {
|
||||
this.debug(`Session id is present, attempting an immediate reconnect...`, shard);
|
||||
this.reconnect();
|
||||
} else {
|
||||
shard.destroy({ reset: true, emit: false, log: false });
|
||||
this.reconnect();
|
||||
}
|
||||
});
|
||||
|
||||
shard.on(ShardEvents.InvalidSession, () => {
|
||||
this.client.emit(Events.ShardReconnecting, shard.id);
|
||||
});
|
||||
|
||||
shard.on(ShardEvents.Destroyed, () => {
|
||||
this.debug('Shard was destroyed but no WebSocket connection was present! Reconnecting...', shard);
|
||||
|
||||
this.client.emit(Events.ShardReconnecting, shard.id);
|
||||
|
||||
this.shardQueue.add(shard);
|
||||
this.reconnect();
|
||||
});
|
||||
|
||||
shard.eventsAttached = true;
|
||||
}
|
||||
|
||||
this.shards.set(shard.id, shard);
|
||||
|
||||
try {
|
||||
await shard.connect();
|
||||
} catch (error) {
|
||||
if (error?.code && UNRECOVERABLE_CLOSE_CODES.includes(error.code)) {
|
||||
throw new Error(GatewayCloseCodes[error.code]);
|
||||
// Undefined if session is invalid, error event for regular closes
|
||||
} else if (!error || error.code) {
|
||||
this.debug('Failed to connect to the gateway, requeueing...', shard);
|
||||
this.shardQueue.add(shard);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// If we have more shards, add a 5s delay
|
||||
if (this.shardQueue.size) {
|
||||
this.debug(`Shard Queue Size: ${this.shardQueue.size}; continuing in 5 seconds...`);
|
||||
await sleep(5_000);
|
||||
return this.createShards();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles reconnects for this manager.
|
||||
* @private
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async reconnect() {
|
||||
if (this.reconnecting || this.status !== Status.Ready) return false;
|
||||
this.reconnecting = true;
|
||||
try {
|
||||
await this.createShards();
|
||||
} catch (error) {
|
||||
this.debug(`Couldn't reconnect or fetch information about the gateway. ${error}`);
|
||||
if (error.httpStatus !== 401) {
|
||||
this.debug(`Possible network error occurred. Retrying in 5s...`);
|
||||
await sleep(5_000);
|
||||
this.reconnecting = false;
|
||||
return this.reconnect();
|
||||
}
|
||||
// If we get an error at this point, it means we cannot reconnect anymore
|
||||
if (this.client.listenerCount(Events.Invalidated)) {
|
||||
/**
|
||||
* Emitted when the client's session becomes invalidated.
|
||||
* You are expected to handle closing the process gracefully and preventing a boot loop
|
||||
* if you are listening to this event.
|
||||
* @event Client#invalidated
|
||||
*/
|
||||
this.client.emit(Events.Invalidated);
|
||||
// Destroy just the shards. This means you have to handle the cleanup yourself
|
||||
this.destroy();
|
||||
} else {
|
||||
this.client.destroy();
|
||||
}
|
||||
} finally {
|
||||
this.reconnecting = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a packet to every shard this manager handles.
|
||||
* @param {Object} packet The packet to send
|
||||
* @private
|
||||
*/
|
||||
broadcast(packet) {
|
||||
for (const shard of this.shards.values()) shard.send(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys this manager and all its shards.
|
||||
* @private
|
||||
*/
|
||||
destroy() {
|
||||
if (this.destroyed) return;
|
||||
this.debug(`Manager was destroyed. Called by:\n${new Error('MANAGER_DESTROYED').stack}`);
|
||||
this.destroyed = true;
|
||||
this.shardQueue.clear();
|
||||
for (const shard of this.shards.values()) shard.destroy({ closeCode: 1_000, reset: true, emit: false, log: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, shard });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.packetQueue.length) {
|
||||
const item = this.packetQueue.shift();
|
||||
setImmediate(() => {
|
||||
this.handlePacket(item.packet, item.shard);
|
||||
}).unref();
|
||||
}
|
||||
|
||||
if (packet && PacketHandlers[packet.t]) {
|
||||
PacketHandlers[packet.t](this.client, packet, shard);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the client is ready to be marked as ready.
|
||||
* @private
|
||||
*/
|
||||
checkShardsReady() {
|
||||
if (this.status === Status.Ready) return;
|
||||
if (this.shards.size !== this.totalShards || this.shards.some(s => s.status !== Status.Ready)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.triggerClientReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes the client to be marked as ready and emits the ready event.
|
||||
* @private
|
||||
*/
|
||||
triggerClientReady() {
|
||||
this.status = Status.Ready;
|
||||
|
||||
this.client.readyTimestamp = Date.now();
|
||||
|
||||
/**
|
||||
* Emitted when the client becomes ready to start working.
|
||||
* @event Client#ready
|
||||
* @param {Client} client The client
|
||||
*/
|
||||
this.client.emit(Events.ClientReady, this.client);
|
||||
|
||||
this.handlePacket();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebSocketManager;
|
787
src/client/websocket/WebSocketShard.js
Normal file
787
src/client/websocket/WebSocketShard.js
Normal file
@@ -0,0 +1,787 @@
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('node:events');
|
||||
const { setTimeout, setInterval, clearTimeout, clearInterval } = require('node:timers');
|
||||
const { GatewayDispatchEvents, GatewayIntentBits, GatewayOpcodes } = require('discord-api-types/v9');
|
||||
const WebSocket = require('../../WebSocket');
|
||||
const Events = require('../../util/Events');
|
||||
const IntentsBitField = require('../../util/IntentsBitField');
|
||||
const ShardEvents = require('../../util/ShardEvents');
|
||||
const Status = require('../../util/Status');
|
||||
|
||||
const STATUS_KEYS = Object.keys(Status);
|
||||
const CONNECTION_STATE = Object.keys(WebSocket.WebSocket);
|
||||
|
||||
let zlib;
|
||||
|
||||
try {
|
||||
zlib = require('zlib-sync');
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
|
||||
/**
|
||||
* Represents a Shard's WebSocket connection
|
||||
*/
|
||||
class WebSocketShard extends EventEmitter {
|
||||
constructor(manager, id) {
|
||||
super();
|
||||
|
||||
/**
|
||||
* The WebSocketManager of the shard
|
||||
* @type {WebSocketManager}
|
||||
*/
|
||||
this.manager = manager;
|
||||
|
||||
/**
|
||||
* The shard's id
|
||||
* @type {number}
|
||||
*/
|
||||
this.id = id;
|
||||
|
||||
/**
|
||||
* The current status of the shard
|
||||
* @type {Status}
|
||||
*/
|
||||
this.status = Status.Idle;
|
||||
|
||||
/**
|
||||
* The current sequence of the shard
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.sequence = -1;
|
||||
|
||||
/**
|
||||
* The sequence of the shard after close
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.closeSequence = 0;
|
||||
|
||||
/**
|
||||
* The current session id of the shard
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.sessionId = null;
|
||||
|
||||
/**
|
||||
* The previous heartbeat ping of the shard
|
||||
* @type {number}
|
||||
*/
|
||||
this.ping = -1;
|
||||
|
||||
/**
|
||||
* The last time a ping was sent (a timestamp)
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.lastPingTimestamp = -1;
|
||||
|
||||
/**
|
||||
* If we received a heartbeat ack back. Used to identify zombie connections
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.lastHeartbeatAcked = true;
|
||||
|
||||
/**
|
||||
* Contains the rate limit queue and metadata
|
||||
* @name WebSocketShard#ratelimit
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
Object.defineProperty(this, 'ratelimit', {
|
||||
value: {
|
||||
queue: [],
|
||||
total: 120,
|
||||
remaining: 120,
|
||||
time: 60e3,
|
||||
timer: null,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* The WebSocket connection for the current shard
|
||||
* @name WebSocketShard#connection
|
||||
* @type {?WebSocket}
|
||||
* @private
|
||||
*/
|
||||
Object.defineProperty(this, 'connection', { value: null, writable: true });
|
||||
|
||||
/**
|
||||
* @external Inflate
|
||||
* @see {@link https://www.npmjs.com/package/zlib-sync}
|
||||
*/
|
||||
|
||||
/**
|
||||
* The compression to use
|
||||
* @name WebSocketShard#inflate
|
||||
* @type {?Inflate}
|
||||
* @private
|
||||
*/
|
||||
Object.defineProperty(this, 'inflate', { value: null, writable: true });
|
||||
|
||||
/**
|
||||
* The HELLO timeout
|
||||
* @name WebSocketShard#helloTimeout
|
||||
* @type {?NodeJS.Timeout}
|
||||
* @private
|
||||
*/
|
||||
Object.defineProperty(this, 'helloTimeout', { value: null, writable: true });
|
||||
|
||||
/**
|
||||
* If the manager attached its event handlers on the shard
|
||||
* @name WebSocketShard#eventsAttached
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
Object.defineProperty(this, 'eventsAttached', { value: false, writable: true });
|
||||
|
||||
/**
|
||||
* A set of guild ids this shard expects to receive
|
||||
* @name WebSocketShard#expectedGuilds
|
||||
* @type {?Set<string>}
|
||||
* @private
|
||||
*/
|
||||
Object.defineProperty(this, 'expectedGuilds', { value: null, writable: true });
|
||||
|
||||
/**
|
||||
* The ready timeout
|
||||
* @name WebSocketShard#readyTimeout
|
||||
* @type {?NodeJS.Timeout}
|
||||
* @private
|
||||
*/
|
||||
Object.defineProperty(this, 'readyTimeout', { value: null, writable: true });
|
||||
|
||||
/**
|
||||
* Time when the WebSocket connection was opened
|
||||
* @name WebSocketShard#connectedAt
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
Object.defineProperty(this, 'connectedAt', { value: 0, writable: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a debug event.
|
||||
* @param {string} message The debug message
|
||||
* @private
|
||||
*/
|
||||
debug(message) {
|
||||
this.manager.debug(message, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects the shard to the gateway.
|
||||
* @private
|
||||
* @returns {Promise<void>} A promise that will resolve if the shard turns ready successfully,
|
||||
* or reject if we couldn't connect
|
||||
*/
|
||||
connect() {
|
||||
const { gateway, client } = this.manager;
|
||||
|
||||
if (this.connection?.readyState === WebSocket.OPEN && this.status === Status.Ready) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const cleanup = () => {
|
||||
this.removeListener(ShardEvents.Close, onClose);
|
||||
this.removeListener(ShardEvents.Ready, onReady);
|
||||
this.removeListener(ShardEvents.Resumed, onResumed);
|
||||
this.removeListener(ShardEvents.InvalidSession, onInvalidOrDestroyed);
|
||||
this.removeListener(ShardEvents.Destroyed, onInvalidOrDestroyed);
|
||||
};
|
||||
|
||||
const onReady = () => {
|
||||
cleanup();
|
||||
resolve();
|
||||
};
|
||||
|
||||
const onResumed = () => {
|
||||
cleanup();
|
||||
resolve();
|
||||
};
|
||||
|
||||
const onClose = event => {
|
||||
cleanup();
|
||||
reject(event);
|
||||
};
|
||||
|
||||
const onInvalidOrDestroyed = () => {
|
||||
cleanup();
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
reject();
|
||||
};
|
||||
|
||||
this.once(ShardEvents.Ready, onReady);
|
||||
this.once(ShardEvents.Resumed, onResumed);
|
||||
this.once(ShardEvents.Close, onClose);
|
||||
this.once(ShardEvents.InvalidSession, onInvalidOrDestroyed);
|
||||
this.once(ShardEvents.Destroyed, onInvalidOrDestroyed);
|
||||
|
||||
if (this.connection?.readyState === WebSocket.OPEN) {
|
||||
this.debug('An open connection was found, attempting an immediate identify.');
|
||||
this.identify();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.connection) {
|
||||
this.debug(`A connection object was found. Cleaning up before continuing.
|
||||
State: ${CONNECTION_STATE[this.connection.readyState]}`);
|
||||
this.destroy({ emit: false });
|
||||
}
|
||||
|
||||
const wsQuery = { v: client.options.ws.version };
|
||||
|
||||
if (zlib) {
|
||||
this.inflate = new zlib.Inflate({
|
||||
chunkSize: 65535,
|
||||
flush: zlib.Z_SYNC_FLUSH,
|
||||
to: WebSocket.encoding === 'json' ? 'string' : '',
|
||||
});
|
||||
wsQuery.compress = 'zlib-stream';
|
||||
}
|
||||
|
||||
this.debug(
|
||||
`[CONNECT]
|
||||
Gateway : ${gateway}
|
||||
Version : ${client.options.ws.version}
|
||||
Encoding : ${WebSocket.encoding}
|
||||
Compression: ${zlib ? 'zlib-stream' : 'none'}`,
|
||||
);
|
||||
|
||||
this.status = this.status === Status.Disconnected ? Status.Reconnecting : Status.Connecting;
|
||||
this.setHelloTimeout();
|
||||
|
||||
this.connectedAt = Date.now();
|
||||
|
||||
const ws = (this.connection = WebSocket.create(gateway, wsQuery));
|
||||
ws.onopen = this.onOpen.bind(this);
|
||||
ws.onmessage = this.onMessage.bind(this);
|
||||
ws.onerror = this.onError.bind(this);
|
||||
ws.onclose = this.onClose.bind(this);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a connection is opened to the gateway.
|
||||
* @private
|
||||
*/
|
||||
onOpen() {
|
||||
this.debug(`[CONNECTED] Took ${Date.now() - this.connectedAt}ms`);
|
||||
this.status = Status.Nearly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a message is received.
|
||||
* @param {MessageEvent} event Event received
|
||||
* @private
|
||||
*/
|
||||
onMessage({ data }) {
|
||||
let raw;
|
||||
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
|
||||
if (zlib) {
|
||||
const l = data.length;
|
||||
const flush =
|
||||
l >= 4 && data[l - 4] === 0x00 && data[l - 3] === 0x00 && data[l - 2] === 0xff && data[l - 1] === 0xff;
|
||||
|
||||
this.inflate.push(data, flush && zlib.Z_SYNC_FLUSH);
|
||||
if (!flush) return;
|
||||
raw = this.inflate.result;
|
||||
} else {
|
||||
raw = data;
|
||||
}
|
||||
let packet;
|
||||
try {
|
||||
packet = WebSocket.unpack(raw);
|
||||
} catch (err) {
|
||||
this.manager.client.emit(Events.ShardError, err, this.id);
|
||||
return;
|
||||
}
|
||||
this.manager.client.emit(Events.Raw, packet, this.id);
|
||||
if (packet.op === GatewayOpcodes.Dispatch) this.manager.emit(packet.t, packet.d, this.id);
|
||||
this.onPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever an error occurs with the WebSocket.
|
||||
* @param {ErrorEvent} event The error that occurred
|
||||
* @private
|
||||
*/
|
||||
onError(event) {
|
||||
const error = event?.error ?? event;
|
||||
if (!error) return;
|
||||
|
||||
/**
|
||||
* Emitted whenever a shard's WebSocket encounters a connection error.
|
||||
* @event Client#shardError
|
||||
* @param {Error} error The encountered error
|
||||
* @param {number} shardId The shard that encountered this error
|
||||
*/
|
||||
this.manager.client.emit(Events.ShardError, error, this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @external CloseEvent
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @external ErrorEvent
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @external MessageEvent
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called whenever a connection to the gateway is closed.
|
||||
* @param {CloseEvent} event Close event that was received
|
||||
* @private
|
||||
*/
|
||||
onClose(event) {
|
||||
if (this.sequence !== -1) this.closeSequence = this.sequence;
|
||||
this.sequence = -1;
|
||||
|
||||
this.debug(`[CLOSE]
|
||||
Event Code: ${event.code}
|
||||
Clean : ${event.wasClean}
|
||||
Reason : ${event.reason ?? 'No reason received'}`);
|
||||
|
||||
this.setHeartbeatTimer(-1);
|
||||
this.setHelloTimeout(-1);
|
||||
// If we still have a connection object, clean up its listeners
|
||||
if (this.connection) this._cleanupConnection();
|
||||
|
||||
this.status = Status.Disconnected;
|
||||
|
||||
/**
|
||||
* Emitted when a shard's WebSocket closes.
|
||||
* @private
|
||||
* @event WebSocketShard#close
|
||||
* @param {CloseEvent} event The received event
|
||||
*/
|
||||
this.emit(ShardEvents.Close, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a packet is received.
|
||||
* @param {Object} packet The received packet
|
||||
* @private
|
||||
*/
|
||||
onPacket(packet) {
|
||||
if (!packet) {
|
||||
this.debug(`Received broken packet: '${packet}'.`);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (packet.t) {
|
||||
case GatewayDispatchEvents.Ready:
|
||||
/**
|
||||
* Emitted when the shard receives the READY payload and is now waiting for guilds
|
||||
* @event WebSocketShard#ready
|
||||
*/
|
||||
this.emit(ShardEvents.Ready);
|
||||
|
||||
this.sessionId = packet.d.session_id;
|
||||
this.expectedGuilds = new Set(packet.d.guilds.map(d => d.id));
|
||||
this.status = Status.WaitingForGuilds;
|
||||
this.debug(`[READY] Session ${this.sessionId}.`);
|
||||
this.lastHeartbeatAcked = true;
|
||||
this.sendHeartbeat('ReadyHeartbeat');
|
||||
break;
|
||||
case GatewayDispatchEvents.Resumed: {
|
||||
/**
|
||||
* Emitted when the shard resumes successfully
|
||||
* @event WebSocketShard#resumed
|
||||
*/
|
||||
this.emit(ShardEvents.Resumed);
|
||||
|
||||
this.status = Status.Ready;
|
||||
const replayed = packet.s - this.closeSequence;
|
||||
this.debug(`[RESUMED] Session ${this.sessionId} | Replayed ${replayed} events.`);
|
||||
this.lastHeartbeatAcked = true;
|
||||
this.sendHeartbeat('ResumeHeartbeat');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.s > this.sequence) this.sequence = packet.s;
|
||||
|
||||
switch (packet.op) {
|
||||
case GatewayOpcodes.Hello:
|
||||
this.setHelloTimeout(-1);
|
||||
this.setHeartbeatTimer(packet.d.heartbeat_interval);
|
||||
this.identify();
|
||||
break;
|
||||
case GatewayOpcodes.Reconnect:
|
||||
this.debug('[RECONNECT] Discord asked us to reconnect');
|
||||
this.destroy({ closeCode: 4_000 });
|
||||
break;
|
||||
case GatewayOpcodes.InvalidSession:
|
||||
this.debug(`[INVALID SESSION] Resumable: ${packet.d}.`);
|
||||
// If we can resume the session, do so immediately
|
||||
if (packet.d) {
|
||||
this.identifyResume();
|
||||
return;
|
||||
}
|
||||
// Reset the sequence
|
||||
this.sequence = -1;
|
||||
// Reset the session id as it's invalid
|
||||
this.sessionId = null;
|
||||
// Set the status to reconnecting
|
||||
this.status = Status.Reconnecting;
|
||||
// Finally, emit the INVALID_SESSION event
|
||||
this.emit(ShardEvents.InvalidSession);
|
||||
break;
|
||||
case GatewayOpcodes.HeartbeatAck:
|
||||
this.ackHeartbeat();
|
||||
break;
|
||||
case GatewayOpcodes.Heartbeat:
|
||||
this.sendHeartbeat('HeartbeatRequest', true);
|
||||
break;
|
||||
default:
|
||||
this.manager.handlePacket(packet, this);
|
||||
if (this.status === Status.WaitingForGuilds && packet.t === GatewayDispatchEvents.GuildCreate) {
|
||||
this.expectedGuilds.delete(packet.d.id);
|
||||
this.checkReady();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the shard can be marked as ready
|
||||
* @private
|
||||
*/
|
||||
checkReady() {
|
||||
// Step 0. Clear the ready timeout, if it exists
|
||||
if (this.readyTimeout) {
|
||||
clearTimeout(this.readyTimeout);
|
||||
this.readyTimeout = null;
|
||||
}
|
||||
// Step 1. If we don't have any other guilds pending, we are ready
|
||||
if (!this.expectedGuilds.size) {
|
||||
this.debug('Shard received all its guilds. Marking as fully ready.');
|
||||
this.status = Status.Ready;
|
||||
|
||||
/**
|
||||
* Emitted when the shard is fully ready.
|
||||
* This event is emitted if:
|
||||
* * all guilds were received by this shard
|
||||
* * the ready timeout expired, and some guilds are unavailable
|
||||
* @event WebSocketShard#allReady
|
||||
* @param {?Set<string>} unavailableGuilds Set of unavailable guilds, if any
|
||||
*/
|
||||
this.emit(ShardEvents.AllReady);
|
||||
return;
|
||||
}
|
||||
const hasGuildsIntent = new IntentsBitField(this.manager.client.options.intents).has(GatewayIntentBits.Guilds);
|
||||
// Step 2. Create a timeout that will mark the shard as ready if there are still unavailable guilds
|
||||
// * The timeout is 15 seconds by default
|
||||
// * This can be optionally changed in the client options via the `waitGuildTimeout` option
|
||||
// * a timeout time of zero will skip this timeout, which potentially could cause the Client to miss guilds.
|
||||
|
||||
const { waitGuildTimeout } = this.manager.client.options;
|
||||
|
||||
this.readyTimeout = setTimeout(
|
||||
() => {
|
||||
this.debug(
|
||||
`Shard ${hasGuildsIntent ? 'did' : 'will'} not receive any more guild packets` +
|
||||
`${hasGuildsIntent ? ` in ${waitGuildTimeout} ms` : ''}.\nUnavailable guild count: ${
|
||||
this.expectedGuilds.size
|
||||
}`,
|
||||
);
|
||||
|
||||
this.readyTimeout = null;
|
||||
|
||||
this.status = Status.Ready;
|
||||
|
||||
this.emit(ShardEvents.AllReady, this.expectedGuilds);
|
||||
},
|
||||
0,
|
||||
).unref();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HELLO packet timeout.
|
||||
* @param {number} [time] If set to -1, it will clear the hello timeout
|
||||
* @private
|
||||
*/
|
||||
setHelloTimeout(time) {
|
||||
if (time === -1) {
|
||||
if (this.helloTimeout) {
|
||||
this.debug('Clearing the HELLO timeout.');
|
||||
clearTimeout(this.helloTimeout);
|
||||
this.helloTimeout = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.debug('Setting a HELLO timeout for 20s.');
|
||||
this.helloTimeout = setTimeout(() => {
|
||||
this.debug('Did not receive HELLO in time. Destroying and connecting again.');
|
||||
this.destroy({ reset: true, closeCode: 4009 });
|
||||
}, 20_000).unref();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the heartbeat timer for this shard.
|
||||
* @param {number} time If -1, clears the interval, any other number sets an interval
|
||||
* @private
|
||||
*/
|
||||
setHeartbeatTimer(time) {
|
||||
if (time === -1) {
|
||||
if (this.heartbeatInterval) {
|
||||
this.debug('Clearing the heartbeat interval.');
|
||||
clearInterval(this.heartbeatInterval);
|
||||
this.heartbeatInterval = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.debug(`Setting a heartbeat interval for ${time}ms.`);
|
||||
// Sanity checks
|
||||
if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
|
||||
this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), time).unref();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a heartbeat to the WebSocket.
|
||||
* If this shard didn't receive a heartbeat last time, it will destroy it and reconnect
|
||||
* @param {string} [tag='HeartbeatTimer'] What caused this heartbeat to be sent
|
||||
* @param {boolean} [ignoreHeartbeatAck] If we should send the heartbeat forcefully.
|
||||
* @private
|
||||
*/
|
||||
sendHeartbeat(
|
||||
tag = 'HeartbeatTimer',
|
||||
ignoreHeartbeatAck = [Status.WaitingForGuilds, Status.Identifying, Status.Resuming].includes(this.status),
|
||||
) {
|
||||
if (ignoreHeartbeatAck && !this.lastHeartbeatAcked) {
|
||||
this.debug(`[${tag}] Didn't process heartbeat ack yet but we are still connected. Sending one now.`);
|
||||
} else if (!this.lastHeartbeatAcked) {
|
||||
this.debug(
|
||||
`[${tag}] Didn't receive a heartbeat ack last time, assuming zombie connection. Destroying and reconnecting.
|
||||
Status : ${STATUS_KEYS[this.status]}
|
||||
Sequence : ${this.sequence}
|
||||
Connection State: ${this.connection ? CONNECTION_STATE[this.connection.readyState] : 'No Connection??'}`,
|
||||
);
|
||||
|
||||
this.destroy({ closeCode: 4009, reset: true });
|
||||
return;
|
||||
}
|
||||
|
||||
this.debug(`[${tag}] Sending a heartbeat.`);
|
||||
this.lastHeartbeatAcked = false;
|
||||
this.lastPingTimestamp = Date.now();
|
||||
this.send({ op: GatewayOpcodes.Heartbeat, d: this.sequence }, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledges a heartbeat.
|
||||
* @private
|
||||
*/
|
||||
ackHeartbeat() {
|
||||
this.lastHeartbeatAcked = true;
|
||||
const latency = Date.now() - this.lastPingTimestamp;
|
||||
this.debug(`Heartbeat acknowledged, latency of ${latency}ms.`);
|
||||
this.ping = latency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the client on the connection.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
identify() {
|
||||
return this.sessionId ? this.identifyResume() : this.identifyNew();
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies as a new connection on the gateway.
|
||||
* @private
|
||||
*/
|
||||
identifyNew() {
|
||||
const { client } = this.manager;
|
||||
if (!client.token) {
|
||||
this.debug('[IDENTIFY] No token available to identify a new session.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.status = Status.Identifying;
|
||||
|
||||
// Clone the identify payload and assign the token and shard info
|
||||
const d = {
|
||||
...client.options.ws,
|
||||
token: client.token,
|
||||
};
|
||||
|
||||
this.debug(`[IDENTIFY] Shard ${this.id}/${client.options.shardCount} with intents: 32767`);
|
||||
this.send({ op: GatewayOpcodes.Identify, d }, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes a session on the gateway.
|
||||
* @private
|
||||
*/
|
||||
identifyResume() {
|
||||
if (!this.sessionId) {
|
||||
this.debug('[RESUME] No session id was present; identifying as a new session.');
|
||||
this.identifyNew();
|
||||
return;
|
||||
}
|
||||
|
||||
this.status = Status.Resuming;
|
||||
|
||||
this.debug(`[RESUME] Session ${this.sessionId}, sequence ${this.closeSequence}`);
|
||||
|
||||
const d = {
|
||||
token: this.manager.client.token,
|
||||
session_id: this.sessionId,
|
||||
seq: this.closeSequence,
|
||||
};
|
||||
|
||||
this.send({ op: GatewayOpcodes.Resume, d }, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a packet to the queue to be sent to the gateway.
|
||||
* <warn>If you use this method, make sure you understand that you need to provide
|
||||
* a full [Payload](https://discord.com/developers/docs/topics/gateway#commands-and-events-gateway-commands).
|
||||
* Do not use this method if you don't know what you're doing.</warn>
|
||||
* @param {Object} data The full packet to send
|
||||
* @param {boolean} [important=false] If this packet should be added first in queue
|
||||
*/
|
||||
send(data, important = false) {
|
||||
this.ratelimit.queue[important ? 'unshift' : 'push'](data);
|
||||
this.processQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data, bypassing the queue.
|
||||
* @param {Object} data Packet to send
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
_send(data) {
|
||||
if (this.connection?.readyState !== WebSocket.OPEN) {
|
||||
this.debug(`Tried to send packet '${JSON.stringify(data)}' but no WebSocket is available!`);
|
||||
this.destroy({ closeCode: 4_000 });
|
||||
return;
|
||||
}
|
||||
|
||||
this.connection.send(WebSocket.pack(data), err => {
|
||||
if (err) this.manager.client.emit(Events.ShardError, err, this.id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.timer = setTimeout(() => {
|
||||
this.ratelimit.remaining = this.ratelimit.total;
|
||||
this.processQueue();
|
||||
}, this.ratelimit.time).unref();
|
||||
}
|
||||
while (this.ratelimit.remaining > 0) {
|
||||
const item = this.ratelimit.queue.shift();
|
||||
if (!item) return;
|
||||
this._send(item);
|
||||
this.ratelimit.remaining--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys this shard and closes its WebSocket connection.
|
||||
* @param {Object} [options={ closeCode: 1000, reset: false, emit: true, log: true }] Options for destroying the shard
|
||||
* @private
|
||||
*/
|
||||
destroy({ closeCode = 1_000, reset = false, emit = true, log = true } = {}) {
|
||||
if (log) {
|
||||
this.debug(`[DESTROY]
|
||||
Close Code : ${closeCode}
|
||||
Reset : ${reset}
|
||||
Emit DESTROYED: ${emit}`);
|
||||
}
|
||||
|
||||
// Step 0: Remove all timers
|
||||
this.setHeartbeatTimer(-1);
|
||||
this.setHelloTimeout(-1);
|
||||
|
||||
// Step 1: Close the WebSocket connection, if any, otherwise, emit DESTROYED
|
||||
if (this.connection) {
|
||||
// If the connection is currently opened, we will (hopefully) receive close
|
||||
if (this.connection.readyState === WebSocket.OPEN) {
|
||||
this.connection.close(closeCode);
|
||||
} else {
|
||||
// Connection is not OPEN
|
||||
this.debug(`WS State: ${CONNECTION_STATE[this.connection.readyState]}`);
|
||||
// Remove listeners from the connection
|
||||
this._cleanupConnection();
|
||||
// Attempt to close the connection just in case
|
||||
try {
|
||||
this.connection.close(closeCode);
|
||||
} catch {
|
||||
// No-op
|
||||
}
|
||||
// Emit the destroyed event if needed
|
||||
if (emit) this._emitDestroyed();
|
||||
}
|
||||
} else if (emit) {
|
||||
// We requested a destroy, but we had no connection. Emit destroyed
|
||||
this._emitDestroyed();
|
||||
}
|
||||
|
||||
// Step 2: Null the connection object
|
||||
this.connection = null;
|
||||
|
||||
// Step 3: Set the shard status to Disconnected
|
||||
this.status = Status.Disconnected;
|
||||
|
||||
// Step 4: Cache the old sequence (use to attempt a resume)
|
||||
if (this.sequence !== -1) this.closeSequence = this.sequence;
|
||||
|
||||
// Step 5: Reset the sequence and session id if requested
|
||||
if (reset) {
|
||||
this.sequence = -1;
|
||||
this.sessionId = null;
|
||||
}
|
||||
|
||||
// Step 6: reset the rate limit data
|
||||
this.ratelimit.remaining = this.ratelimit.total;
|
||||
this.ratelimit.queue.length = 0;
|
||||
if (this.ratelimit.timer) {
|
||||
clearTimeout(this.ratelimit.timer);
|
||||
this.ratelimit.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the WebSocket connection listeners.
|
||||
* @private
|
||||
*/
|
||||
_cleanupConnection() {
|
||||
this.connection.onopen = this.connection.onclose = this.connection.onerror = this.connection.onmessage = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits the DESTROYED event on the shard
|
||||
* @private
|
||||
*/
|
||||
_emitDestroyed() {
|
||||
/**
|
||||
* Emitted when a shard is destroyed, but no WebSocket connection was present.
|
||||
* @private
|
||||
* @event WebSocketShard#destroyed
|
||||
*/
|
||||
this.emit(ShardEvents.Destroyed);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebSocketShard;
|
5
src/client/websocket/handlers/CHANNEL_CREATE.js
Normal file
5
src/client/websocket/handlers/CHANNEL_CREATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.ChannelCreate.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/CHANNEL_DELETE.js
Normal file
5
src/client/websocket/handlers/CHANNEL_DELETE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.ChannelDelete.handle(packet.d);
|
||||
};
|
22
src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js
Normal file
22
src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
const Events = require('../../../util/Events');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
const channel = client.channels.cache.get(data.channel_id);
|
||||
const time = data.last_pin_timestamp ? Date.parse(data.last_pin_timestamp) : null;
|
||||
|
||||
if (channel) {
|
||||
// Discord sends null for last_pin_timestamp if the last pinned message was removed
|
||||
channel.lastPinTimestamp = time;
|
||||
|
||||
/**
|
||||
* 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 {TextBasedChannels} channel The channel that the pins update occurred in
|
||||
* @param {Date} time The time of the pins update
|
||||
*/
|
||||
client.emit(Events.ChannelPinsUpdate, channel, time);
|
||||
}
|
||||
};
|
16
src/client/websocket/handlers/CHANNEL_UPDATE.js
Normal file
16
src/client/websocket/handlers/CHANNEL_UPDATE.js
Normal file
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
const Events = require('../../../util/Events');
|
||||
|
||||
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, channel type change.
|
||||
* @event Client#channelUpdate
|
||||
* @param {DMChannel|GuildChannel} oldChannel The channel before the update
|
||||
* @param {DMChannel|GuildChannel} newChannel The channel after the update
|
||||
*/
|
||||
client.emit(Events.ChannelUpdate, old, updated);
|
||||
}
|
||||
};
|
5
src/client/websocket/handlers/GUILD_BAN_ADD.js
Normal file
5
src/client/websocket/handlers/GUILD_BAN_ADD.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildBanAdd.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/GUILD_BAN_REMOVE.js
Normal file
5
src/client/websocket/handlers/GUILD_BAN_REMOVE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
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 @@
|
||||
'use strict';
|
||||
|
||||
const Events = require('../../../util/Events');
|
||||
const Status = require('../../../util/Status');
|
||||
|
||||
module.exports = (client, { d: data }, shard) => {
|
||||
let guild = client.guilds.cache.get(data.id);
|
||||
if (guild) {
|
||||
if (!guild.available && !data.unavailable) {
|
||||
// A newly available guild
|
||||
guild._patch(data);
|
||||
}
|
||||
} else {
|
||||
// A new guild
|
||||
data.shardId = shard.id;
|
||||
guild = client.guilds._add(data);
|
||||
if (client.ws.status === Status.Ready) {
|
||||
/**
|
||||
* Emitted whenever the client joins a guild.
|
||||
* @event Client#guildCreate
|
||||
* @param {Guild} guild The created guild
|
||||
*/
|
||||
client.emit(Events.GuildCreate, guild);
|
||||
}
|
||||
}
|
||||
};
|
5
src/client/websocket/handlers/GUILD_DELETE.js
Normal file
5
src/client/websocket/handlers/GUILD_DELETE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildDelete.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js
Normal file
5
src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildEmojisUpdate.handle(packet.d);
|
||||
};
|
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildIntegrationsUpdate.handle(packet.d);
|
||||
};
|
36
src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js
Normal file
36
src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js
Normal file
@@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const Events = require('../../../util/Events');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
const guild = client.guilds.cache.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));
|
||||
if (data.presences) {
|
||||
for (const presence of data.presences) guild.presences._add(Object.assign(presence, { guild }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the properties of a guild members chunk
|
||||
* @typedef {Object} GuildMembersChunk
|
||||
* @property {number} index Index of the received chunk
|
||||
* @property {number} count Number of chunks the client should receive
|
||||
* @property {?string} nonce Nonce for this chunk
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param {GuildMembersChunk} chunk Properties of the received chunk
|
||||
*/
|
||||
client.emit(Events.GuildMembersChunk, members, guild, {
|
||||
count: data.chunk_count,
|
||||
index: data.chunk_index,
|
||||
nonce: data.nonce,
|
||||
});
|
||||
};
|
20
src/client/websocket/handlers/GUILD_MEMBER_ADD.js
Normal file
20
src/client/websocket/handlers/GUILD_MEMBER_ADD.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
const Events = require('../../../util/Events');
|
||||
const Status = require('../../../util/Status');
|
||||
|
||||
module.exports = (client, { d: data }, shard) => {
|
||||
const guild = client.guilds.cache.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.GuildMemberAdd, member);
|
||||
}
|
||||
}
|
||||
};
|
5
src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js
Normal file
5
src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet, shard) => {
|
||||
client.actions.GuildMemberRemove.handle(packet.d, shard);
|
||||
};
|
5
src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js
Normal file
5
src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet, shard) => {
|
||||
client.actions.GuildMemberUpdate.handle(packet.d, shard);
|
||||
};
|
5
src/client/websocket/handlers/GUILD_ROLE_CREATE.js
Normal file
5
src/client/websocket/handlers/GUILD_ROLE_CREATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildRoleCreate.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/GUILD_ROLE_DELETE.js
Normal file
5
src/client/websocket/handlers/GUILD_ROLE_DELETE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildRoleDelete.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/GUILD_ROLE_UPDATE.js
Normal file
5
src/client/websocket/handlers/GUILD_ROLE_UPDATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildRoleUpdate.handle(packet.d);
|
||||
};
|
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildScheduledEventCreate.handle(packet.d);
|
||||
};
|
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildScheduledEventDelete.handle(packet.d);
|
||||
};
|
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildScheduledEventUpdate.handle(packet.d);
|
||||
};
|
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildScheduledEventUserAdd.handle(packet.d);
|
||||
};
|
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildScheduledEventUserRemove.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/GUILD_STICKERS_UPDATE.js
Normal file
5
src/client/websocket/handlers/GUILD_STICKERS_UPDATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildStickersUpdate.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/GUILD_UPDATE.js
Normal file
5
src/client/websocket/handlers/GUILD_UPDATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildUpdate.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/INTERACTION_CREATE.js
Normal file
5
src/client/websocket/handlers/INTERACTION_CREATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.InteractionCreate.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/INVITE_CREATE.js
Normal file
5
src/client/websocket/handlers/INVITE_CREATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.InviteCreate.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/INVITE_DELETE.js
Normal file
5
src/client/websocket/handlers/INVITE_DELETE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.InviteDelete.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/MESSAGE_CREATE.js
Normal file
5
src/client/websocket/handlers/MESSAGE_CREATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageCreate.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/MESSAGE_DELETE.js
Normal file
5
src/client/websocket/handlers/MESSAGE_DELETE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageDelete.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/MESSAGE_DELETE_BULK.js
Normal file
5
src/client/websocket/handlers/MESSAGE_DELETE_BULK.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageDeleteBulk.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/MESSAGE_REACTION_ADD.js
Normal file
5
src/client/websocket/handlers/MESSAGE_REACTION_ADD.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageReactionAdd.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js
Normal file
5
src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageReactionRemove.handle(packet.d);
|
||||
};
|
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageReactionRemoveAll.handle(packet.d);
|
||||
};
|
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageReactionRemoveEmoji.handle(packet.d);
|
||||
};
|
16
src/client/websocket/handlers/MESSAGE_UPDATE.js
Normal file
16
src/client/websocket/handlers/MESSAGE_UPDATE.js
Normal file
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
const Events = require('../../../util/Events');
|
||||
|
||||
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.MessageUpdate, old, updated);
|
||||
}
|
||||
};
|
5
src/client/websocket/handlers/PRESENCE_UPDATE.js
Normal file
5
src/client/websocket/handlers/PRESENCE_UPDATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.PresenceUpdate.handle(packet.d);
|
||||
};
|
41
src/client/websocket/handlers/READY.js
Normal file
41
src/client/websocket/handlers/READY.js
Normal file
@@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
const ClientApplication = require('../../../structures/ClientApplication');
|
||||
const User = require('../../../structures/User');
|
||||
let ClientUser;
|
||||
|
||||
module.exports = (client, { d: data }, shard) => {
|
||||
//console.log(data);
|
||||
|
||||
client.session_id = data.session_id;
|
||||
if (client.user) {
|
||||
client.user._patch(data.user);
|
||||
} else {
|
||||
ClientUser ??= require('../../../structures/ClientUser');
|
||||
client.user = new ClientUser(client, data.user);
|
||||
client.users.cache.set(client.user.id, client.user);
|
||||
}
|
||||
|
||||
client.user.setAFK(true);
|
||||
|
||||
for (const guild of data.guilds) {
|
||||
guild.shardId = shard.id;
|
||||
client.guilds._add(guild);
|
||||
}
|
||||
|
||||
for (const r of data.relationships) {
|
||||
if(r.type == 1) {
|
||||
client.friends.cache.set(r.id, new User(client, r.user));
|
||||
} else if(r.type == 2) {
|
||||
client.blocked.cache.set(r.id, new User(client, r.user));
|
||||
}
|
||||
}
|
||||
|
||||
if (client.application) {
|
||||
client.application._patch(data.application);
|
||||
} else {
|
||||
client.application = new ClientApplication(client, data.application);
|
||||
}
|
||||
|
||||
shard.checkReady();
|
||||
};
|
14
src/client/websocket/handlers/RESUMED.js
Normal file
14
src/client/websocket/handlers/RESUMED.js
Normal file
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
const Events = require('../../../util/Events');
|
||||
|
||||
module.exports = (client, packet, shard) => {
|
||||
const replayed = shard.sequence - shard.closeSequence;
|
||||
/**
|
||||
* Emitted when a shard resumes successfully.
|
||||
* @event Client#shardResume
|
||||
* @param {number} id The shard id that resumed
|
||||
* @param {number} replayedEvents The amount of replayed events
|
||||
*/
|
||||
client.emit(Events.ShardResume, shard.id, replayed);
|
||||
};
|
5
src/client/websocket/handlers/STAGE_INSTANCE_CREATE.js
Normal file
5
src/client/websocket/handlers/STAGE_INSTANCE_CREATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.StageInstanceCreate.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/STAGE_INSTANCE_DELETE.js
Normal file
5
src/client/websocket/handlers/STAGE_INSTANCE_DELETE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.StageInstanceDelete.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/STAGE_INSTANCE_UPDATE.js
Normal file
5
src/client/websocket/handlers/STAGE_INSTANCE_UPDATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.StageInstanceUpdate.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/THREAD_CREATE.js
Normal file
5
src/client/websocket/handlers/THREAD_CREATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.ThreadCreate.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/THREAD_DELETE.js
Normal file
5
src/client/websocket/handlers/THREAD_DELETE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.ThreadDelete.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/THREAD_LIST_SYNC.js
Normal file
5
src/client/websocket/handlers/THREAD_LIST_SYNC.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.ThreadListSync.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/THREAD_MEMBERS_UPDATE.js
Normal file
5
src/client/websocket/handlers/THREAD_MEMBERS_UPDATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.ThreadMembersUpdate.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/THREAD_MEMBER_UPDATE.js
Normal file
5
src/client/websocket/handlers/THREAD_MEMBER_UPDATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.ThreadMemberUpdate.handle(packet.d);
|
||||
};
|
16
src/client/websocket/handlers/THREAD_UPDATE.js
Normal file
16
src/client/websocket/handlers/THREAD_UPDATE.js
Normal file
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
const Events = require('../../../util/Events');
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
const { old, updated } = client.actions.ChannelUpdate.handle(packet.d);
|
||||
if (old && updated) {
|
||||
/**
|
||||
* Emitted whenever a thread is updated - e.g. name change, archive state change, locked state change.
|
||||
* @event Client#threadUpdate
|
||||
* @param {ThreadChannel} oldThread The thread before the update
|
||||
* @param {ThreadChannel} newThread The thread after the update
|
||||
*/
|
||||
client.emit(Events.ThreadUpdate, old, updated);
|
||||
}
|
||||
};
|
5
src/client/websocket/handlers/TYPING_START.js
Normal file
5
src/client/websocket/handlers/TYPING_START.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.TypingStart.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/USER_UPDATE.js
Normal file
5
src/client/websocket/handlers/USER_UPDATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.UserUpdate.handle(packet.d);
|
||||
};
|
6
src/client/websocket/handlers/VOICE_SERVER_UPDATE.js
Normal file
6
src/client/websocket/handlers/VOICE_SERVER_UPDATE.js
Normal file
@@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.emit('debug', `[VOICE] received voice server: ${JSON.stringify(packet)}`);
|
||||
client.voice.onVoiceServer(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/VOICE_STATE_UPDATE.js
Normal file
5
src/client/websocket/handlers/VOICE_STATE_UPDATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.VoiceStateUpdate.handle(packet.d);
|
||||
};
|
5
src/client/websocket/handlers/WEBHOOKS_UPDATE.js
Normal file
5
src/client/websocket/handlers/WEBHOOKS_UPDATE.js
Normal file
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.WebhooksUpdate.handle(packet.d);
|
||||
};
|
58
src/client/websocket/handlers/index.js
Normal file
58
src/client/websocket/handlers/index.js
Normal file
@@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
const handlers = Object.fromEntries([
|
||||
['READY', require('./READY')],
|
||||
['RESUMED', require('./RESUMED')],
|
||||
['GUILD_CREATE', require('./GUILD_CREATE')],
|
||||
['GUILD_DELETE', require('./GUILD_DELETE')],
|
||||
['GUILD_UPDATE', require('./GUILD_UPDATE')],
|
||||
['INVITE_CREATE', require('./INVITE_CREATE')],
|
||||
['INVITE_DELETE', require('./INVITE_DELETE')],
|
||||
['GUILD_MEMBER_ADD', require('./GUILD_MEMBER_ADD')],
|
||||
['GUILD_MEMBER_REMOVE', require('./GUILD_MEMBER_REMOVE')],
|
||||
['GUILD_MEMBER_UPDATE', require('./GUILD_MEMBER_UPDATE')],
|
||||
['GUILD_MEMBERS_CHUNK', require('./GUILD_MEMBERS_CHUNK')],
|
||||
['GUILD_INTEGRATIONS_UPDATE', require('./GUILD_INTEGRATIONS_UPDATE')],
|
||||
['GUILD_ROLE_CREATE', require('./GUILD_ROLE_CREATE')],
|
||||
['GUILD_ROLE_DELETE', require('./GUILD_ROLE_DELETE')],
|
||||
['GUILD_ROLE_UPDATE', require('./GUILD_ROLE_UPDATE')],
|
||||
['GUILD_BAN_ADD', require('./GUILD_BAN_ADD')],
|
||||
['GUILD_BAN_REMOVE', require('./GUILD_BAN_REMOVE')],
|
||||
['GUILD_EMOJIS_UPDATE', require('./GUILD_EMOJIS_UPDATE')],
|
||||
['CHANNEL_CREATE', require('./CHANNEL_CREATE')],
|
||||
['CHANNEL_DELETE', require('./CHANNEL_DELETE')],
|
||||
['CHANNEL_UPDATE', require('./CHANNEL_UPDATE')],
|
||||
['CHANNEL_PINS_UPDATE', require('./CHANNEL_PINS_UPDATE')],
|
||||
['MESSAGE_CREATE', require('./MESSAGE_CREATE')],
|
||||
['MESSAGE_DELETE', require('./MESSAGE_DELETE')],
|
||||
['MESSAGE_UPDATE', require('./MESSAGE_UPDATE')],
|
||||
['MESSAGE_DELETE_BULK', require('./MESSAGE_DELETE_BULK')],
|
||||
['MESSAGE_REACTION_ADD', require('./MESSAGE_REACTION_ADD')],
|
||||
['MESSAGE_REACTION_REMOVE', require('./MESSAGE_REACTION_REMOVE')],
|
||||
['MESSAGE_REACTION_REMOVE_ALL', require('./MESSAGE_REACTION_REMOVE_ALL')],
|
||||
['MESSAGE_REACTION_REMOVE_EMOJI', require('./MESSAGE_REACTION_REMOVE_EMOJI')],
|
||||
['THREAD_CREATE', require('./THREAD_CREATE')],
|
||||
['THREAD_UPDATE', require('./THREAD_UPDATE')],
|
||||
['THREAD_DELETE', require('./THREAD_DELETE')],
|
||||
['THREAD_LIST_SYNC', require('./THREAD_LIST_SYNC')],
|
||||
['THREAD_MEMBER_UPDATE', require('./THREAD_MEMBER_UPDATE')],
|
||||
['THREAD_MEMBERS_UPDATE', require('./THREAD_MEMBERS_UPDATE')],
|
||||
['USER_UPDATE', require('./USER_UPDATE')],
|
||||
['PRESENCE_UPDATE', require('./PRESENCE_UPDATE')],
|
||||
['TYPING_START', require('./TYPING_START')],
|
||||
['VOICE_STATE_UPDATE', require('./VOICE_STATE_UPDATE')],
|
||||
['VOICE_SERVER_UPDATE', require('./VOICE_SERVER_UPDATE')],
|
||||
['WEBHOOKS_UPDATE', require('./WEBHOOKS_UPDATE')],
|
||||
['INTERACTION_CREATE', require('./INTERACTION_CREATE')],
|
||||
['STAGE_INSTANCE_CREATE', require('./STAGE_INSTANCE_CREATE')],
|
||||
['STAGE_INSTANCE_UPDATE', require('./STAGE_INSTANCE_UPDATE')],
|
||||
['STAGE_INSTANCE_DELETE', require('./STAGE_INSTANCE_DELETE')],
|
||||
['GUILD_STICKERS_UPDATE', require('./GUILD_STICKERS_UPDATE')],
|
||||
['GUILD_SCHEDULED_EVENT_CREATE', require('./GUILD_SCHEDULED_EVENT_CREATE')],
|
||||
['GUILD_SCHEDULED_EVENT_UPDATE', require('./GUILD_SCHEDULED_EVENT_UPDATE')],
|
||||
['GUILD_SCHEDULED_EVENT_DELETE', require('./GUILD_SCHEDULED_EVENT_DELETE')],
|
||||
['GUILD_SCHEDULED_EVENT_USER_ADD', require('./GUILD_SCHEDULED_EVENT_USER_ADD')],
|
||||
['GUILD_SCHEDULED_EVENT_USER_REMOVE', require('./GUILD_SCHEDULED_EVENT_USER_REMOVE')],
|
||||
]);
|
||||
|
||||
module.exports = handlers;
|
Reference in New Issue
Block a user