mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
refactor!: fully integrate /ws into mainlib (#10420)
BREAKING CHANGE: `Client#ws` is now a `@discordjs/ws#WebSocketManager` BREAKING CHANGE: `WebSocketManager` and `WebSocketShard` are now re-exports from `@discordjs/ws` BREAKING CHANGE: Removed the `WebSocketShardEvents` enum BREAKING CHANGE: Renamed the `Client#ready` event to `Client#clientReady` event to not confuse it with the gateway `READY` event BREAKING CHANGE: Added `Client#ping` to replace the old `WebSocketManager#ping` BREAKING CHANGE: Removed the `Shard#reconnecting` event which wasn’t emitted anymore since 14.8.0 anyway BREAKING CHANGE: Removed `ShardClientUtil#ids` and `ShardClientUtil#count` in favor of `Client#ws#getShardIds()` and `Client#ws#getShardCount()` BREAKING CHANGE: `ClientUser#setPresence()` and `ClientPresence#set()` now return a Promise which resolves when the gateway call was sent successfully BREAKING CHANGE: Removed `Guild#shard` as `WebSocketShard`s are now handled by `@discordjs/ws` BREAKING CHANGE: Removed the following deprecated `Client` events: `raw`, `shardDisconnect`, `shardError`, `shardReady`, `shardReconnecting`, `shardResume` in favor of events from `@discordjs/ws#WebSocketManager` BREAKING CHANGE: Removed `ClientOptions#shards` and `ClientOptions#shardCount` in favor of `ClientOptions#ws#shardIds` and `ClientOptions#ws#shardCount`
This commit is contained in:
@@ -70,7 +70,7 @@
|
||||
"@discordjs/formatters": "workspace:^",
|
||||
"@discordjs/rest": "workspace:^",
|
||||
"@discordjs/util": "workspace:^",
|
||||
"@discordjs/ws": "1.1.1",
|
||||
"@discordjs/ws": "workspace:^",
|
||||
"@sapphire/snowflake": "3.5.3",
|
||||
"discord-api-types": "^0.37.101",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
const process = require('node:process');
|
||||
const { clearTimeout, setImmediate, setTimeout } = require('node:timers');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { makeURLSearchParams } = require('@discordjs/rest');
|
||||
const { OAuth2Scopes, Routes } = require('discord-api-types/v10');
|
||||
const { WebSocketManager, WebSocketShardEvents, WebSocketShardStatus } = require('@discordjs/ws');
|
||||
const { GatewayDispatchEvents, GatewayIntentBits, OAuth2Scopes, Routes } = require('discord-api-types/v10');
|
||||
const BaseClient = require('./BaseClient');
|
||||
const ActionsManager = require('./actions/ActionsManager');
|
||||
const ClientVoiceManager = require('./voice/ClientVoiceManager');
|
||||
const WebSocketManager = require('./websocket/WebSocketManager');
|
||||
const { DiscordjsError, DiscordjsTypeError, DiscordjsRangeError, ErrorCodes } = require('../errors');
|
||||
const PacketHandlers = require('./websocket/handlers');
|
||||
const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors');
|
||||
const BaseGuildEmojiManager = require('../managers/BaseGuildEmojiManager');
|
||||
const ChannelManager = require('../managers/ChannelManager');
|
||||
const GuildManager = require('../managers/GuildManager');
|
||||
@@ -31,6 +33,17 @@ const PermissionsBitField = require('../util/PermissionsBitField');
|
||||
const Status = require('../util/Status');
|
||||
const Sweepers = require('../util/Sweepers');
|
||||
|
||||
const WaitingForGuildEvents = [GatewayDispatchEvents.GuildCreate, GatewayDispatchEvents.GuildDelete];
|
||||
const BeforeReadyWhitelist = [
|
||||
GatewayDispatchEvents.Ready,
|
||||
GatewayDispatchEvents.Resumed,
|
||||
GatewayDispatchEvents.GuildCreate,
|
||||
GatewayDispatchEvents.GuildDelete,
|
||||
GatewayDispatchEvents.GuildMembersChunk,
|
||||
GatewayDispatchEvents.GuildMemberAdd,
|
||||
GatewayDispatchEvents.GuildMemberRemove,
|
||||
];
|
||||
|
||||
/**
|
||||
* The main hub for interacting with the Discord API, and the starting point for any bot.
|
||||
* @extends {BaseClient}
|
||||
@@ -45,43 +58,45 @@ class Client extends BaseClient {
|
||||
const data = require('node:worker_threads').workerData ?? process.env;
|
||||
const defaults = Options.createDefault();
|
||||
|
||||
if (this.options.shards === defaults.shards) {
|
||||
if ('SHARDS' in data) {
|
||||
this.options.shards = JSON.parse(data.SHARDS);
|
||||
}
|
||||
if (this.options.ws.shardIds === defaults.ws.shardIds && 'SHARDS' in data) {
|
||||
this.options.ws.shardIds = JSON.parse(data.SHARDS);
|
||||
}
|
||||
|
||||
if (this.options.shardCount === defaults.shardCount) {
|
||||
if ('SHARD_COUNT' in data) {
|
||||
this.options.shardCount = Number(data.SHARD_COUNT);
|
||||
} else if (Array.isArray(this.options.shards)) {
|
||||
this.options.shardCount = this.options.shards.length;
|
||||
}
|
||||
if (this.options.ws.shardCount === defaults.ws.shardCount && 'SHARD_COUNT' in data) {
|
||||
this.options.ws.shardCount = Number(data.SHARD_COUNT);
|
||||
}
|
||||
|
||||
const typeofShards = typeof this.options.shards;
|
||||
|
||||
if (typeofShards === 'undefined' && typeof this.options.shardCount === 'number') {
|
||||
this.options.shards = Array.from({ length: this.options.shardCount }, (_, i) => i);
|
||||
}
|
||||
|
||||
if (typeofShards === 'number') this.options.shards = [this.options.shards];
|
||||
|
||||
if (Array.isArray(this.options.shards)) {
|
||||
this.options.shards = [
|
||||
...new Set(
|
||||
this.options.shards.filter(item => !isNaN(item) && item >= 0 && item < Infinity && item === (item | 0)),
|
||||
),
|
||||
];
|
||||
}
|
||||
/**
|
||||
* The presence of the Client
|
||||
* @private
|
||||
* @type {ClientPresence}
|
||||
*/
|
||||
this.presence = new ClientPresence(this, this.options.ws.initialPresence ?? this.options.presence);
|
||||
|
||||
this._validateOptions();
|
||||
|
||||
/**
|
||||
* The WebSocket manager of the client
|
||||
* @type {WebSocketManager}
|
||||
* The current status of this Client
|
||||
* @type {Status}
|
||||
* @private
|
||||
*/
|
||||
this.ws = new WebSocketManager(this);
|
||||
this.status = Status.Idle;
|
||||
|
||||
/**
|
||||
* A set of guild ids this Client expects to receive
|
||||
* @name Client#expectedGuilds
|
||||
* @type {Set<string>}
|
||||
* @private
|
||||
*/
|
||||
Object.defineProperty(this, 'expectedGuilds', { value: new Set(), writable: true });
|
||||
|
||||
/**
|
||||
* The ready timeout
|
||||
* @name Client#readyTimeout
|
||||
* @type {?NodeJS.Timeout}
|
||||
* @private
|
||||
*/
|
||||
Object.defineProperty(this, 'readyTimeout', { value: null, writable: true });
|
||||
|
||||
/**
|
||||
* The action manager of the client
|
||||
@@ -90,12 +105,6 @@ class Client extends BaseClient {
|
||||
*/
|
||||
this.actions = new ActionsManager(this);
|
||||
|
||||
/**
|
||||
* The voice manager of the client
|
||||
* @type {ClientVoiceManager}
|
||||
*/
|
||||
this.voice = new ClientVoiceManager(this);
|
||||
|
||||
/**
|
||||
* Shard helpers for the client (only if the process was spawned from a {@link ShardingManager})
|
||||
* @type {?ShardClientUtil}
|
||||
@@ -119,7 +128,7 @@ class Client extends BaseClient {
|
||||
|
||||
/**
|
||||
* All of the {@link BaseChannel}s that the client is currently handling, mapped by their ids -
|
||||
* as long as sharding isn't being used, this will be *every* channel in *every* guild the bot
|
||||
* as long as no sharding manager is being used, this will be *every* channel in *every* guild the bot
|
||||
* is a member of. Note that DM channels will not be initially cached, and thus not be present
|
||||
* in the Manager without their explicit fetching or use.
|
||||
* @type {ChannelManager}
|
||||
@@ -132,13 +141,6 @@ class Client extends BaseClient {
|
||||
*/
|
||||
this.sweepers = new Sweepers(this, this.options.sweepers);
|
||||
|
||||
/**
|
||||
* The presence of the Client
|
||||
* @private
|
||||
* @type {ClientPresence}
|
||||
*/
|
||||
this.presence = new ClientPresence(this, this.options.presence);
|
||||
|
||||
Object.defineProperty(this, 'token', { writable: true });
|
||||
if (!this.token && 'DISCORD_TOKEN' in process.env) {
|
||||
/**
|
||||
@@ -148,10 +150,31 @@ class Client extends BaseClient {
|
||||
* @type {?string}
|
||||
*/
|
||||
this.token = process.env.DISCORD_TOKEN;
|
||||
} else if (this.options.ws.token) {
|
||||
this.token = this.options.ws.token;
|
||||
} else {
|
||||
this.token = null;
|
||||
}
|
||||
|
||||
const wsOptions = {
|
||||
...this.options.ws,
|
||||
intents: this.options.intents.bitfield,
|
||||
rest: this.rest,
|
||||
token: this.token,
|
||||
};
|
||||
|
||||
/**
|
||||
* The WebSocket manager of the client
|
||||
* @type {WebSocketManager}
|
||||
*/
|
||||
this.ws = new WebSocketManager(wsOptions);
|
||||
|
||||
/**
|
||||
* The voice manager of the client
|
||||
* @type {ClientVoiceManager}
|
||||
*/
|
||||
this.voice = new ClientVoiceManager(this);
|
||||
|
||||
/**
|
||||
* User that the client is logged in as
|
||||
* @type {?ClientUser}
|
||||
@@ -164,11 +187,33 @@ class Client extends BaseClient {
|
||||
*/
|
||||
this.application = null;
|
||||
|
||||
/**
|
||||
* The latencies of the WebSocketShard connections
|
||||
* @type {Collection<number, number>}
|
||||
*/
|
||||
this.pings = new Collection();
|
||||
|
||||
/**
|
||||
* The last time a ping was sent (a timestamp) for each WebSocketShard connection
|
||||
* @type {Collection<number,number>}
|
||||
*/
|
||||
this.lastPingTimestamps = new Collection();
|
||||
|
||||
/**
|
||||
* Timestamp of the time the client was last {@link Status.Ready} at
|
||||
* @type {?number}
|
||||
*/
|
||||
this.readyTimestamp = null;
|
||||
|
||||
/**
|
||||
* An array of queued events before this Client became ready
|
||||
* @type {Object[]}
|
||||
* @private
|
||||
* @name Client#incomingPacketQueue
|
||||
*/
|
||||
Object.defineProperty(this, 'incomingPacketQueue', { value: [] });
|
||||
|
||||
this._attachEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,13 +260,10 @@ class Client extends BaseClient {
|
||||
this.token = token = token.replace(/^(Bot|Bearer)\s*/i, '');
|
||||
this.rest.setToken(token);
|
||||
this.emit(Events.Debug, `Provided token: ${this._censoredToken}`);
|
||||
|
||||
if (this.options.presence) {
|
||||
this.options.ws.presence = this.presence._parse(this.options.presence);
|
||||
}
|
||||
|
||||
this.emit(Events.Debug, 'Preparing to connect to the gateway...');
|
||||
|
||||
this.ws.setToken(this.token);
|
||||
|
||||
try {
|
||||
await this.ws.connect();
|
||||
return this.token;
|
||||
@@ -231,13 +273,150 @@ class Client extends BaseClient {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the client can be marked as ready
|
||||
* @private
|
||||
*/
|
||||
async _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 &&
|
||||
(await this.ws.fetchStatus()).every(status => status === WebSocketShardStatus.Ready)
|
||||
) {
|
||||
this.emit(Events.Debug, 'Client received all its guilds. Marking as fully ready.');
|
||||
this.status = Status.Ready;
|
||||
|
||||
this._triggerClientReady();
|
||||
return;
|
||||
}
|
||||
const hasGuildsIntent = this.options.intents.has(GatewayIntentBits.Guilds);
|
||||
// Step 2. Create a timeout that will mark the client 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.
|
||||
|
||||
this.readyTimeout = setTimeout(
|
||||
() => {
|
||||
this.emit(
|
||||
Events.Debug,
|
||||
`${
|
||||
hasGuildsIntent
|
||||
? `Client did not receive any guild packets in ${this.options.waitGuildTimeout} ms.`
|
||||
: 'Client will not receive anymore guild packets.'
|
||||
}\nUnavailable guild count: ${this.expectedGuilds.size}`,
|
||||
);
|
||||
|
||||
this.readyTimeout = null;
|
||||
this.status = Status.Ready;
|
||||
|
||||
this._triggerClientReady();
|
||||
},
|
||||
hasGuildsIntent ? this.options.waitGuildTimeout : 0,
|
||||
).unref();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches event handlers to the WebSocketShardManager from `@discordjs/ws`.
|
||||
* @private
|
||||
*/
|
||||
_attachEvents() {
|
||||
this.ws.on(WebSocketShardEvents.Debug, (message, shardId) =>
|
||||
this.emit(Events.Debug, `[WS => ${typeof shardId === 'number' ? `Shard ${shardId}` : 'Manager'}] ${message}`),
|
||||
);
|
||||
this.ws.on(WebSocketShardEvents.Dispatch, this._handlePacket.bind(this));
|
||||
|
||||
this.ws.on(WebSocketShardEvents.Ready, data => {
|
||||
for (const guild of data.guilds) {
|
||||
this.expectedGuilds.add(guild.id);
|
||||
}
|
||||
this.status = Status.WaitingForGuilds;
|
||||
this._checkReady();
|
||||
});
|
||||
|
||||
this.ws.on(WebSocketShardEvents.HeartbeatComplete, ({ heartbeatAt, latency }, shardId) => {
|
||||
this.emit(Events.Debug, `[WS => Shard ${shardId}] Heartbeat acknowledged, latency of ${latency}ms.`);
|
||||
this.lastPingTimestamps.set(shardId, heartbeatAt);
|
||||
this.pings.set(shardId, latency);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a packet and queues it if this WebSocketManager is not ready.
|
||||
* @param {GatewayDispatchPayload} packet The packet to be handled
|
||||
* @param {number} shardId The shardId that received this packet
|
||||
* @private
|
||||
*/
|
||||
_handlePacket(packet, shardId) {
|
||||
if (this.status !== Status.Ready && !BeforeReadyWhitelist.includes(packet.t)) {
|
||||
this.incomingPacketQueue.push({ packet, shardId });
|
||||
} else {
|
||||
if (this.incomingPacketQueue.length) {
|
||||
const item = this.incomingPacketQueue.shift();
|
||||
setImmediate(() => {
|
||||
this._handlePacket(item.packet, item.shardId);
|
||||
}).unref();
|
||||
}
|
||||
|
||||
if (PacketHandlers[packet.t]) {
|
||||
PacketHandlers[packet.t](this, packet, shardId);
|
||||
}
|
||||
|
||||
if (this.status === Status.WaitingForGuilds && WaitingForGuildEvents.includes(packet.t)) {
|
||||
this.expectedGuilds.delete(packet.d.id);
|
||||
this._checkReady();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a packet to every shard of this client handles.
|
||||
* @param {Object} packet The packet to send
|
||||
* @private
|
||||
*/
|
||||
async _broadcast(packet) {
|
||||
const shardIds = await this.ws.getShardIds();
|
||||
return Promise.all(shardIds.map(shardId => this.ws.send(shardId, packet)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes the client to be marked as ready and emits the ready event.
|
||||
* @private
|
||||
*/
|
||||
_triggerClientReady() {
|
||||
this.status = Status.Ready;
|
||||
|
||||
this.readyTimestamp = Date.now();
|
||||
|
||||
/**
|
||||
* Emitted when the client becomes ready to start working.
|
||||
* @event Client#clientReady
|
||||
* @param {Client} client The client
|
||||
*/
|
||||
this.emit(Events.ClientReady, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the client has logged in, indicative of being able to access
|
||||
* properties such as `user` and `application`.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isReady() {
|
||||
return !this.ws.destroyed && this.ws.status === Status.Ready;
|
||||
return this.status === Status.Ready;
|
||||
}
|
||||
|
||||
/**
|
||||
* The average ping of all WebSocketShards
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get ping() {
|
||||
const sum = this.pings.reduce((a, b) => a + b, 0);
|
||||
return sum / this.pings.size;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -505,20 +684,10 @@ class Client extends BaseClient {
|
||||
* @private
|
||||
*/
|
||||
_validateOptions(options = this.options) {
|
||||
if (options.intents === undefined) {
|
||||
if (options.intents === undefined && options.ws?.intents === undefined) {
|
||||
throw new DiscordjsTypeError(ErrorCodes.ClientMissingIntents);
|
||||
} else {
|
||||
options.intents = new IntentsBitField(options.intents).freeze();
|
||||
}
|
||||
if (typeof options.shardCount !== 'number' || isNaN(options.shardCount) || options.shardCount < 1) {
|
||||
throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'shardCount', 'a number greater than or equal to 1');
|
||||
}
|
||||
if (options.shards && !(options.shards === 'auto' || Array.isArray(options.shards))) {
|
||||
throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'shards', "'auto', a number or array of numbers");
|
||||
}
|
||||
if (options.shards && !options.shards.length) throw new DiscordjsRangeError(ErrorCodes.ClientInvalidProvidedShards);
|
||||
if (typeof options.makeCache !== 'function') {
|
||||
throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'makeCache', 'a function');
|
||||
options.intents = new IntentsBitField(options.intents ?? options.ws.intents).freeze();
|
||||
}
|
||||
if (typeof options.sweepers !== 'object' || options.sweepers === null) {
|
||||
throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'sweepers', 'an object');
|
||||
@@ -541,12 +710,17 @@ class Client extends BaseClient {
|
||||
) {
|
||||
throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'allowedMentions', 'an object');
|
||||
}
|
||||
if (typeof options.presence !== 'object' || options.presence === null) {
|
||||
throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'presence', 'an object');
|
||||
}
|
||||
if (typeof options.ws !== 'object' || options.ws === null) {
|
||||
throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'ws', 'an object');
|
||||
}
|
||||
if (
|
||||
(typeof options.presence !== 'object' || options.presence === null) &&
|
||||
options.ws.initialPresence === undefined
|
||||
) {
|
||||
throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'presence', 'an object');
|
||||
} else {
|
||||
options.ws.initialPresence = options.ws.initialPresence ?? this.presence._parse(this.options.presence);
|
||||
}
|
||||
if (typeof options.rest !== 'object' || options.rest === null) {
|
||||
throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'rest', 'an object');
|
||||
}
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
const Action = require('./Action');
|
||||
const Events = require('../../util/Events');
|
||||
const Status = require('../../util/Status');
|
||||
|
||||
class GuildMemberRemoveAction extends Action {
|
||||
handle(data, shard) {
|
||||
handle(data) {
|
||||
const client = this.client;
|
||||
const guild = client.guilds.cache.get(data.guild_id);
|
||||
let member = null;
|
||||
@@ -19,7 +18,7 @@ class GuildMemberRemoveAction extends Action {
|
||||
* @event Client#guildMemberRemove
|
||||
* @param {GuildMember} member The member that has left/been kicked from the guild
|
||||
*/
|
||||
if (shard.status === Status.Ready) client.emit(Events.GuildMemberRemove, member);
|
||||
client.emit(Events.GuildMemberRemove, member);
|
||||
}
|
||||
guild.presences.cache.delete(data.user.id);
|
||||
guild.voiceStates.cache.delete(data.user.id);
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
const Action = require('./Action');
|
||||
const Events = require('../../util/Events');
|
||||
const Status = require('../../util/Status');
|
||||
|
||||
class GuildMemberUpdateAction extends Action {
|
||||
handle(data, shard) {
|
||||
handle(data) {
|
||||
const { client } = this;
|
||||
if (data.user.username) {
|
||||
const user = client.users.cache.get(data.user.id);
|
||||
@@ -27,7 +26,7 @@ class GuildMemberUpdateAction extends Action {
|
||||
* @param {GuildMember} oldMember The member before the update
|
||||
* @param {GuildMember} newMember The member after the update
|
||||
*/
|
||||
if (shard.status === Status.Ready && !member.equals(old)) client.emit(Events.GuildMemberUpdate, old, member);
|
||||
if (!member.equals(old)) client.emit(Events.GuildMemberUpdate, old, member);
|
||||
} else {
|
||||
const newMember = guild.members._add(data);
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const Events = require('../../util/Events');
|
||||
const { WebSocketShardEvents, CloseCodes } = require('@discordjs/ws');
|
||||
|
||||
/**
|
||||
* Manages voice connections for the client
|
||||
@@ -21,10 +21,12 @@ class ClientVoiceManager {
|
||||
*/
|
||||
this.adapters = new Map();
|
||||
|
||||
client.on(Events.ShardDisconnect, (_, shardId) => {
|
||||
for (const [guildId, adapter] of this.adapters.entries()) {
|
||||
if (client.guilds.cache.get(guildId)?.shardId === shardId) {
|
||||
adapter.destroy();
|
||||
client.ws.on(WebSocketShardEvents.Closed, (code, shardId) => {
|
||||
if (code === CloseCodes.Normal) {
|
||||
for (const [guildId, adapter] of this.adapters.entries()) {
|
||||
if (client.guilds.cache.get(guildId)?.shardId === shardId) {
|
||||
adapter.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,387 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('node:events');
|
||||
const process = require('node:process');
|
||||
const { setImmediate } = require('node:timers');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const {
|
||||
WebSocketManager: WSWebSocketManager,
|
||||
WebSocketShardEvents: WSWebSocketShardEvents,
|
||||
CompressionMethod,
|
||||
CloseCodes,
|
||||
} = require('@discordjs/ws');
|
||||
const { GatewayCloseCodes, GatewayDispatchEvents } = require('discord-api-types/v10');
|
||||
const WebSocketShard = require('./WebSocketShard');
|
||||
const PacketHandlers = require('./handlers');
|
||||
const { DiscordjsError, ErrorCodes } = require('../../errors');
|
||||
const Events = require('../../util/Events');
|
||||
const Status = require('../../util/Status');
|
||||
const WebSocketShardEvents = require('../../util/WebSocketShardEvents');
|
||||
|
||||
let zlib;
|
||||
|
||||
try {
|
||||
zlib = require('zlib-sync');
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
|
||||
const BeforeReadyWhitelist = [
|
||||
GatewayDispatchEvents.Ready,
|
||||
GatewayDispatchEvents.Resumed,
|
||||
GatewayDispatchEvents.GuildCreate,
|
||||
GatewayDispatchEvents.GuildDelete,
|
||||
GatewayDispatchEvents.GuildMembersChunk,
|
||||
GatewayDispatchEvents.GuildMemberAdd,
|
||||
GatewayDispatchEvents.GuildMemberRemove,
|
||||
];
|
||||
|
||||
const WaitingForGuildEvents = [GatewayDispatchEvents.GuildCreate, GatewayDispatchEvents.GuildDelete];
|
||||
|
||||
const UNRESUMABLE_CLOSE_CODES = [
|
||||
CloseCodes.Normal,
|
||||
GatewayCloseCodes.AlreadyAuthenticated,
|
||||
GatewayCloseCodes.InvalidSeq,
|
||||
];
|
||||
|
||||
const reasonIsDeprecated = 'the reason property is deprecated, use the code property to determine the reason';
|
||||
let deprecationEmittedForInvalidSessionEvent = false;
|
||||
let deprecationEmittedForDestroyedEvent = false;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* A collection of all shards this manager handles
|
||||
* @type {Collection<number, WebSocketShard>}
|
||||
*/
|
||||
this.shards = new Collection();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The internal WebSocketManager from `@discordjs/ws`.
|
||||
* @type {WSWebSocketManager}
|
||||
* @private
|
||||
*/
|
||||
this._ws = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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[]} messages The debug message
|
||||
* @param {?number} [shardId] The id of the shard that emitted this message, if any
|
||||
* @private
|
||||
*/
|
||||
debug(messages, shardId) {
|
||||
this.client.emit(
|
||||
Events.Debug,
|
||||
`[WS => ${typeof shardId === 'number' ? `Shard ${shardId}` : 'Manager'}] ${messages.join('\n\t')}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects this manager to the gateway.
|
||||
* @private
|
||||
*/
|
||||
async connect() {
|
||||
const invalidToken = new DiscordjsError(ErrorCodes.TokenInvalid);
|
||||
const { shards, shardCount, intents, ws } = this.client.options;
|
||||
if (this._ws && this._ws.options.token !== this.client.token) {
|
||||
await this._ws.destroy({ code: CloseCodes.Normal, reason: 'Login with differing token requested' });
|
||||
this._ws = null;
|
||||
}
|
||||
if (!this._ws) {
|
||||
const wsOptions = {
|
||||
intents: intents.bitfield,
|
||||
rest: this.client.rest,
|
||||
token: this.client.token,
|
||||
largeThreshold: ws.large_threshold,
|
||||
version: ws.version,
|
||||
shardIds: shards === 'auto' ? null : shards,
|
||||
shardCount: shards === 'auto' ? null : shardCount,
|
||||
initialPresence: ws.presence,
|
||||
retrieveSessionInfo: shardId => this.shards.get(shardId).sessionInfo,
|
||||
updateSessionInfo: (shardId, sessionInfo) => {
|
||||
this.shards.get(shardId).sessionInfo = sessionInfo;
|
||||
},
|
||||
compression: zlib ? CompressionMethod.ZlibStream : null,
|
||||
};
|
||||
if (ws.buildIdentifyThrottler) wsOptions.buildIdentifyThrottler = ws.buildIdentifyThrottler;
|
||||
if (ws.buildStrategy) wsOptions.buildStrategy = ws.buildStrategy;
|
||||
this._ws = new WSWebSocketManager(wsOptions);
|
||||
this.attachEvents();
|
||||
}
|
||||
|
||||
const {
|
||||
url: gatewayURL,
|
||||
shards: recommendedShards,
|
||||
session_start_limit: sessionStartLimit,
|
||||
} = await this._ws.fetchGatewayInformation().catch(error => {
|
||||
throw error.status === 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}/`;
|
||||
|
||||
this.client.options.shardCount = await this._ws.getShardCount();
|
||||
this.client.options.shards = await this._ws.getShardIds();
|
||||
this.totalShards = this.client.options.shards.length;
|
||||
for (const id of this.client.options.shards) {
|
||||
if (!this.shards.has(id)) {
|
||||
const shard = new WebSocketShard(this, id);
|
||||
this.shards.set(id, shard);
|
||||
|
||||
shard.on(WebSocketShardEvents.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);
|
||||
|
||||
this.checkShardsReady();
|
||||
});
|
||||
shard.status = Status.Connecting;
|
||||
}
|
||||
}
|
||||
|
||||
await this._ws.connect();
|
||||
|
||||
this.shards.forEach(shard => {
|
||||
if (shard.listenerCount(WebSocketShardEvents.InvalidSession) > 0 && !deprecationEmittedForInvalidSessionEvent) {
|
||||
process.emitWarning(
|
||||
'The WebSocketShard#invalidSession event is deprecated and will never emit.',
|
||||
'DeprecationWarning',
|
||||
);
|
||||
|
||||
deprecationEmittedForInvalidSessionEvent = true;
|
||||
}
|
||||
if (shard.listenerCount(WebSocketShardEvents.Destroyed) > 0 && !deprecationEmittedForDestroyedEvent) {
|
||||
process.emitWarning(
|
||||
'The WebSocketShard#destroyed event is deprecated and will never emit.',
|
||||
'DeprecationWarning',
|
||||
);
|
||||
|
||||
deprecationEmittedForDestroyedEvent = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches event handlers to the internal WebSocketShardManager from `@discordjs/ws`.
|
||||
* @private
|
||||
*/
|
||||
attachEvents() {
|
||||
this._ws.on(WSWebSocketShardEvents.Debug, ({ message, shardId }) => this.debug([message], shardId));
|
||||
this._ws.on(WSWebSocketShardEvents.Dispatch, ({ data, shardId }) => {
|
||||
this.client.emit(Events.Raw, data, shardId);
|
||||
this.emit(data.t, data.d, shardId);
|
||||
const shard = this.shards.get(shardId);
|
||||
this.handlePacket(data, shard);
|
||||
if (shard.status === Status.WaitingForGuilds && WaitingForGuildEvents.includes(data.t)) {
|
||||
shard.gotGuild(data.d.id);
|
||||
}
|
||||
});
|
||||
|
||||
this._ws.on(WSWebSocketShardEvents.Ready, ({ data, shardId }) => {
|
||||
this.shards.get(shardId).onReadyPacket(data);
|
||||
});
|
||||
|
||||
this._ws.on(WSWebSocketShardEvents.Closed, ({ code, shardId }) => {
|
||||
const shard = this.shards.get(shardId);
|
||||
shard.emit(WebSocketShardEvents.Close, { code, reason: reasonIsDeprecated, wasClean: true });
|
||||
if (UNRESUMABLE_CLOSE_CODES.includes(code) && this.destroyed) {
|
||||
shard.status = Status.Disconnected;
|
||||
/**
|
||||
* 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, { code, reason: reasonIsDeprecated, wasClean: true }, shardId);
|
||||
this.debug([`Shard not resumable: ${code} (${GatewayCloseCodes[code] ?? CloseCodes[code]})`], shardId);
|
||||
return;
|
||||
}
|
||||
|
||||
this.shards.get(shardId).status = Status.Connecting;
|
||||
/**
|
||||
* 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, shardId);
|
||||
});
|
||||
this._ws.on(WSWebSocketShardEvents.Hello, ({ shardId }) => {
|
||||
const shard = this.shards.get(shardId);
|
||||
if (shard.sessionInfo) {
|
||||
shard.closeSequence = shard.sessionInfo.sequence;
|
||||
shard.status = Status.Resuming;
|
||||
} else {
|
||||
shard.status = Status.Identifying;
|
||||
}
|
||||
});
|
||||
|
||||
this._ws.on(WSWebSocketShardEvents.Resumed, ({ shardId }) => {
|
||||
const shard = this.shards.get(shardId);
|
||||
shard.status = Status.Ready;
|
||||
/**
|
||||
* Emitted when the shard resumes successfully
|
||||
* @event WebSocketShard#resumed
|
||||
*/
|
||||
shard.emit(WebSocketShardEvents.Resumed);
|
||||
});
|
||||
|
||||
this._ws.on(WSWebSocketShardEvents.HeartbeatComplete, ({ heartbeatAt, latency, shardId }) => {
|
||||
this.debug([`Heartbeat acknowledged, latency of ${latency}ms.`], shardId);
|
||||
const shard = this.shards.get(shardId);
|
||||
shard.lastPingTimestamp = heartbeatAt;
|
||||
shard.ping = latency;
|
||||
});
|
||||
|
||||
this._ws.on(WSWebSocketShardEvents.Error, ({ error, shardId }) => {
|
||||
/**
|
||||
* 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.client.emit(Events.ShardError, error, shardId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a packet to every shard this manager handles.
|
||||
* @param {Object} packet The packet to send
|
||||
* @private
|
||||
*/
|
||||
broadcast(packet) {
|
||||
for (const shardId of this.shards.keys()) this._ws.send(shardId, packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys this manager and all its shards.
|
||||
* @private
|
||||
*/
|
||||
async destroy() {
|
||||
if (this.destroyed) return;
|
||||
// TODO: Make a util for getting a stack
|
||||
this.debug([Object.assign(new Error(), { name: 'Manager was destroyed:' }).stack]);
|
||||
this.destroyed = true;
|
||||
await this._ws?.destroy({ code: CloseCodes.Normal, reason: 'Manager was destroyed' });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(shard => shard.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;
|
||||
@@ -1,234 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('node:events');
|
||||
const process = require('node:process');
|
||||
const { setTimeout, clearTimeout } = require('node:timers');
|
||||
const { GatewayIntentBits } = require('discord-api-types/v10');
|
||||
const Status = require('../../util/Status');
|
||||
const WebSocketShardEvents = require('../../util/WebSocketShardEvents');
|
||||
|
||||
let deprecationEmittedForImportant = false;
|
||||
/**
|
||||
* Represents a Shard's WebSocket connection
|
||||
* @extends {EventEmitter}
|
||||
*/
|
||||
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 sequence of the shard after close
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.closeSequence = 0;
|
||||
|
||||
/**
|
||||
* The previous heartbeat ping of the shard
|
||||
* @type {number}
|
||||
*/
|
||||
this.ping = -1;
|
||||
|
||||
/**
|
||||
* The last time a ping was sent (a timestamp)
|
||||
* @type {number}
|
||||
*/
|
||||
this.lastPingTimestamp = -1;
|
||||
|
||||
/**
|
||||
* 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 });
|
||||
|
||||
/**
|
||||
* @external SessionInfo
|
||||
* @see {@link https://discord.js.org/docs/packages/ws/stable/SessionInfo:Interface}
|
||||
*/
|
||||
|
||||
/**
|
||||
* The session info used by `@discordjs/ws` package.
|
||||
* @name WebSocketShard#sessionInfo
|
||||
* @type {?SessionInfo}
|
||||
* @private
|
||||
*/
|
||||
Object.defineProperty(this, 'sessionInfo', { value: null, writable: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a debug event.
|
||||
* @param {string[]} messages The debug message
|
||||
* @private
|
||||
*/
|
||||
debug(messages) {
|
||||
this.manager.debug(messages, this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @external CloseEvent
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent}
|
||||
*/
|
||||
|
||||
/**
|
||||
* This method is responsible to emit close event for this shard.
|
||||
* This method helps the shard reconnect.
|
||||
* @param {CloseEvent} [event] Close event that was received
|
||||
* @deprecated
|
||||
*/
|
||||
emitClose(
|
||||
event = {
|
||||
code: 1011,
|
||||
reason: 'INTERNAL_ERROR',
|
||||
wasClean: false,
|
||||
},
|
||||
) {
|
||||
this.debug([
|
||||
'[CLOSE]',
|
||||
`Event Code: ${event.code}`,
|
||||
`Clean : ${event.wasClean}`,
|
||||
`Reason : ${event.reason ?? 'No reason received'}`,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Emitted when a shard's WebSocket closes.
|
||||
* @private
|
||||
* @event WebSocketShard#close
|
||||
* @param {CloseEvent} event The received event
|
||||
*/
|
||||
this.emit(WebSocketShardEvents.Close, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the shard receives the READY payload.
|
||||
* @param {Object} packet The received packet
|
||||
* @private
|
||||
*/
|
||||
onReadyPacket(packet) {
|
||||
if (!packet) {
|
||||
this.debug([`Received broken packet: '${packet}'.`]);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted when the shard receives the READY payload and is now waiting for guilds
|
||||
* @event WebSocketShard#ready
|
||||
*/
|
||||
this.emit(WebSocketShardEvents.Ready);
|
||||
|
||||
this.expectedGuilds = new Set(packet.guilds.map(guild => guild.id));
|
||||
this.status = Status.WaitingForGuilds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a GuildCreate or GuildDelete for this shard was sent after READY payload was received,
|
||||
* but before we emitted the READY event.
|
||||
* @param {Snowflake} guildId the id of the Guild sent in the payload
|
||||
* @private
|
||||
*/
|
||||
gotGuild(guildId) {
|
||||
this.expectedGuilds.delete(guildId);
|
||||
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(WebSocketShardEvents.AllReady);
|
||||
return;
|
||||
}
|
||||
const hasGuildsIntent = 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([
|
||||
hasGuildsIntent
|
||||
? `Shard did not receive any guild packets in ${waitGuildTimeout} ms.`
|
||||
: 'Shard will not receive anymore guild packets.',
|
||||
`Unavailable guild count: ${this.expectedGuilds.size}`,
|
||||
]);
|
||||
|
||||
this.readyTimeout = null;
|
||||
this.status = Status.Ready;
|
||||
|
||||
this.emit(WebSocketShardEvents.AllReady, this.expectedGuilds);
|
||||
},
|
||||
hasGuildsIntent ? waitGuildTimeout : 0,
|
||||
).unref();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* <warn>This parameter is **deprecated**. Important payloads are determined by their opcode instead.</warn>
|
||||
*/
|
||||
send(data, important = false) {
|
||||
if (important && !deprecationEmittedForImportant) {
|
||||
process.emitWarning(
|
||||
'Sending important payloads explicitly is deprecated. They are determined by their opcode implicitly now.',
|
||||
'DeprecationWarning',
|
||||
);
|
||||
deprecationEmittedForImportant = true;
|
||||
}
|
||||
this.manager._ws.send(this.id, data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebSocketShard;
|
||||
@@ -3,7 +3,7 @@
|
||||
const Events = require('../../../util/Events');
|
||||
const Status = require('../../../util/Status');
|
||||
|
||||
module.exports = (client, { d: data }, shard) => {
|
||||
module.exports = (client, { d: data }, shardId) => {
|
||||
let guild = client.guilds.cache.get(data.id);
|
||||
if (guild) {
|
||||
if (!guild.available && !data.unavailable) {
|
||||
@@ -19,9 +19,9 @@ module.exports = (client, { d: data }, shard) => {
|
||||
}
|
||||
} else {
|
||||
// A new guild
|
||||
data.shardId = shard.id;
|
||||
data.shardId = shardId;
|
||||
guild = client.guilds._add(data);
|
||||
if (client.ws.status === Status.Ready) {
|
||||
if (client.status === Status.Ready) {
|
||||
/**
|
||||
* Emitted whenever the client joins a guild.
|
||||
* @event Client#guildCreate
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const Events = require('../../../util/Events');
|
||||
const Status = require('../../../util/Status');
|
||||
|
||||
module.exports = (client, { d: data }, shard) => {
|
||||
module.exports = (client, { d: data }) => {
|
||||
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);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet, shard) => {
|
||||
client.actions.GuildMemberRemove.handle(packet.d, shard);
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildMemberRemove.handle(packet.d);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet, shard) => {
|
||||
client.actions.GuildMemberUpdate.handle(packet.d, shard);
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildMemberUpdate.handle(packet.d);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
const ClientApplication = require('../../../structures/ClientApplication');
|
||||
let ClientUser;
|
||||
|
||||
module.exports = (client, { d: data }, shard) => {
|
||||
module.exports = (client, { d: data }, shardId) => {
|
||||
if (client.user) {
|
||||
client.user._patch(data.user);
|
||||
} else {
|
||||
@@ -13,7 +13,7 @@ module.exports = (client, { d: data }, shard) => {
|
||||
}
|
||||
|
||||
for (const guild of data.guilds) {
|
||||
guild.shardId = shard.id;
|
||||
guild.shardId = shardId;
|
||||
client.guilds._add(guild);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,4 @@ module.exports = (client, { d: data }, shard) => {
|
||||
} else {
|
||||
client.application = new ClientApplication(client, data.application);
|
||||
}
|
||||
|
||||
shard.checkReady();
|
||||
};
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const Events = require('../../../util/Events');
|
||||
|
||||
module.exports = (client, packet, shard) => {
|
||||
const replayed = shard.sessionInfo.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);
|
||||
};
|
||||
@@ -49,7 +49,6 @@ const handlers = Object.fromEntries([
|
||||
['MESSAGE_UPDATE', require('./MESSAGE_UPDATE')],
|
||||
['PRESENCE_UPDATE', require('./PRESENCE_UPDATE')],
|
||||
['READY', require('./READY')],
|
||||
['RESUMED', require('./RESUMED')],
|
||||
['STAGE_INSTANCE_CREATE', require('./STAGE_INSTANCE_CREATE')],
|
||||
['STAGE_INSTANCE_DELETE', require('./STAGE_INSTANCE_DELETE')],
|
||||
['STAGE_INSTANCE_UPDATE', require('./STAGE_INSTANCE_UPDATE')],
|
||||
|
||||
@@ -12,8 +12,25 @@
|
||||
* @property {'TokenMissing'} TokenMissing
|
||||
* @property {'ApplicationCommandPermissionsTokenMissing'} ApplicationCommandPermissionsTokenMissing
|
||||
|
||||
* @property {'BitFieldInvalid'} BitFieldInvalid
|
||||
* @property {'WSCloseRequested'} WSCloseRequested
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'WSConnectionExists'} WSConnectionExists
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'WSNotOpen'} WSNotOpen
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'ManagerDestroyed'} ManagerDestroyed
|
||||
* <warn>This property is deprecated.</warn>
|
||||
|
||||
* @property {'BitFieldInvalid'} BitFieldInvalid
|
||||
|
||||
* @property {'ShardingInvalid'} ShardingInvalid
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'ShardingRequired'} ShardingRequired
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'InvalidIntents'} InvalidIntents
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'DisallowedIntents'} DisallowedIntents
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'ShardingNoShards'} ShardingNoShards
|
||||
* @property {'ShardingInProcess'} ShardingInProcess
|
||||
* @property {'ShardingInvalidEvalBroadcast'} ShardingInvalidEvalBroadcast
|
||||
@@ -32,10 +49,30 @@
|
||||
|
||||
* @property {'InviteOptionsMissingChannel'} InviteOptionsMissingChannel
|
||||
|
||||
* @property {'ButtonLabel'} ButtonLabel
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'ButtonURL'} ButtonURL
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'ButtonCustomId'} ButtonCustomId
|
||||
* <warn>This property is deprecated.</warn>
|
||||
|
||||
* @property {'SelectMenuCustomId'} SelectMenuCustomId
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'SelectMenuPlaceholder'} SelectMenuPlaceholder
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'SelectOptionLabel'} SelectOptionLabel
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'SelectOptionValue'} SelectOptionValue
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'SelectOptionDescription'} SelectOptionDescription
|
||||
* <warn>This property is deprecated.</warn>
|
||||
|
||||
* @property {'InteractionCollectorError'} InteractionCollectorError
|
||||
|
||||
* @property {'FileNotFound'} FileNotFound
|
||||
|
||||
* @property {'UserBannerNotFetched'} UserBannerNotFetched
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'UserNoDMChannel'} UserNoDMChannel
|
||||
|
||||
* @property {'VoiceNotStageChannel'} VoiceNotStageChannel
|
||||
@@ -45,11 +82,19 @@
|
||||
|
||||
* @property {'ReqResourceType'} ReqResourceType
|
||||
|
||||
* @property {'ImageFormat'} ImageFormat
|
||||
* <warn>This property is deprecated.</warn>
|
||||
* @property {'ImageSize'} ImageSize
|
||||
* <warn>This property is deprecated.</warn>
|
||||
|
||||
* @property {'MessageBulkDeleteType'} MessageBulkDeleteType
|
||||
* @property {'MessageContentType'} MessageContentType
|
||||
* @property {'MessageNonceRequired'} MessageNonceRequired
|
||||
* @property {'MessageNonceType'} MessageNonceType
|
||||
|
||||
* @property {'SplitMaxLen'} SplitMaxLen
|
||||
* <warn>This property is deprecated.</warn>
|
||||
|
||||
* @property {'BanResolveId'} BanResolveId
|
||||
* @property {'FetchBanResolveId'} FetchBanResolveId
|
||||
|
||||
@@ -83,11 +128,16 @@
|
||||
* @property {'EmojiType'} EmojiType
|
||||
* @property {'EmojiManaged'} EmojiManaged
|
||||
* @property {'MissingManageGuildExpressionsPermission'} MissingManageGuildExpressionsPermission
|
||||
* @property {'MissingManageEmojisAndStickersPermission'} MissingManageEmojisAndStickersPermission
|
||||
* <warn>This property is deprecated. Use `MissingManageGuildExpressionsPermission` instead.</warn>
|
||||
*
|
||||
* @property {'NotGuildSticker'} NotGuildSticker
|
||||
|
||||
* @property {'ReactionResolveUser'} ReactionResolveUser
|
||||
|
||||
* @property {'VanityURL'} VanityURL
|
||||
* <warn>This property is deprecated.</warn>
|
||||
|
||||
* @property {'InviteResolveCode'} InviteResolveCode
|
||||
|
||||
* @property {'InviteNotFound'} InviteNotFound
|
||||
@@ -102,6 +152,8 @@
|
||||
|
||||
* @property {'InteractionAlreadyReplied'} InteractionAlreadyReplied
|
||||
* @property {'InteractionNotReplied'} InteractionNotReplied
|
||||
* @property {'InteractionEphemeralReplied'} InteractionEphemeralReplied
|
||||
* <warn>This property is deprecated.</warn>
|
||||
|
||||
* @property {'CommandInteractionOptionNotFound'} CommandInteractionOptionNotFound
|
||||
* @property {'CommandInteractionOptionType'} CommandInteractionOptionType
|
||||
@@ -140,8 +192,17 @@ const keys = [
|
||||
'TokenMissing',
|
||||
'ApplicationCommandPermissionsTokenMissing',
|
||||
|
||||
'WSCloseRequested',
|
||||
'WSConnectionExists',
|
||||
'WSNotOpen',
|
||||
'ManagerDestroyed',
|
||||
|
||||
'BitFieldInvalid',
|
||||
|
||||
'ShardingInvalid',
|
||||
'ShardingRequired',
|
||||
'InvalidIntents',
|
||||
'DisallowedIntents',
|
||||
'ShardingNoShards',
|
||||
'ShardingInProcess',
|
||||
'ShardingInvalidEvalBroadcast',
|
||||
@@ -160,10 +221,21 @@ const keys = [
|
||||
|
||||
'InviteOptionsMissingChannel',
|
||||
|
||||
'ButtonLabel',
|
||||
'ButtonURL',
|
||||
'ButtonCustomId',
|
||||
|
||||
'SelectMenuCustomId',
|
||||
'SelectMenuPlaceholder',
|
||||
'SelectOptionLabel',
|
||||
'SelectOptionValue',
|
||||
'SelectOptionDescription',
|
||||
|
||||
'InteractionCollectorError',
|
||||
|
||||
'FileNotFound',
|
||||
|
||||
'UserBannerNotFetched',
|
||||
'UserNoDMChannel',
|
||||
|
||||
'VoiceNotStageChannel',
|
||||
@@ -173,11 +245,16 @@ const keys = [
|
||||
|
||||
'ReqResourceType',
|
||||
|
||||
'ImageFormat',
|
||||
'ImageSize',
|
||||
|
||||
'MessageBulkDeleteType',
|
||||
'MessageContentType',
|
||||
'MessageNonceRequired',
|
||||
'MessageNonceType',
|
||||
|
||||
'SplitMaxLen',
|
||||
|
||||
'BanResolveId',
|
||||
'FetchBanResolveId',
|
||||
|
||||
@@ -211,11 +288,14 @@ const keys = [
|
||||
'EmojiType',
|
||||
'EmojiManaged',
|
||||
'MissingManageGuildExpressionsPermission',
|
||||
'MissingManageEmojisAndStickersPermission',
|
||||
|
||||
'NotGuildSticker',
|
||||
|
||||
'ReactionResolveUser',
|
||||
|
||||
'VanityURL',
|
||||
|
||||
'InviteResolveCode',
|
||||
|
||||
'InviteNotFound',
|
||||
@@ -230,6 +310,7 @@ const keys = [
|
||||
|
||||
'InteractionAlreadyReplied',
|
||||
'InteractionNotReplied',
|
||||
'InteractionEphemeralReplied',
|
||||
|
||||
'CommandInteractionOptionNotFound',
|
||||
'CommandInteractionOptionType',
|
||||
|
||||
@@ -48,7 +48,6 @@ exports.SystemChannelFlagsBitField = require('./util/SystemChannelFlagsBitField'
|
||||
exports.ThreadMemberFlagsBitField = require('./util/ThreadMemberFlagsBitField');
|
||||
exports.UserFlagsBitField = require('./util/UserFlagsBitField');
|
||||
__exportStar(require('./util/Util.js'), exports);
|
||||
exports.WebSocketShardEvents = require('./util/WebSocketShardEvents');
|
||||
exports.version = require('../package.json').version;
|
||||
|
||||
// Managers
|
||||
@@ -88,8 +87,6 @@ exports.ThreadManager = require('./managers/ThreadManager');
|
||||
exports.ThreadMemberManager = require('./managers/ThreadMemberManager');
|
||||
exports.UserManager = require('./managers/UserManager');
|
||||
exports.VoiceStateManager = require('./managers/VoiceStateManager');
|
||||
exports.WebSocketManager = require('./client/websocket/WebSocketManager');
|
||||
exports.WebSocketShard = require('./client/websocket/WebSocketShard');
|
||||
|
||||
// Structures
|
||||
exports.ActionRow = require('./structures/ActionRow');
|
||||
|
||||
@@ -273,7 +273,7 @@ class GuildManager extends CachedManager {
|
||||
const data = await this.client.rest.get(Routes.guild(id), {
|
||||
query: makeURLSearchParams({ with_counts: options.withCounts ?? true }),
|
||||
});
|
||||
data.shardId = ShardClientUtil.shardIdForGuildId(id, this.client.options.shardCount);
|
||||
data.shardId = ShardClientUtil.shardIdForGuildId(id, await this.client.ws.fetchShardCount());
|
||||
return this._add(data, options.cache);
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ class GuildMemberManager extends CachedManager {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!query && !users) query = '';
|
||||
this.guild.shard.send({
|
||||
this.guild.client.ws.send(this.guild.shardId, {
|
||||
op: GatewayOpcodes.RequestGuildMembers,
|
||||
d: {
|
||||
guild_id: this.guild.id,
|
||||
|
||||
@@ -352,7 +352,7 @@ class Shard extends EventEmitter {
|
||||
if (message._ready) {
|
||||
this.ready = true;
|
||||
/**
|
||||
* Emitted upon the shard's {@link Client#event:shardReady} event.
|
||||
* Emitted upon the shard's {@link Client#event:clientReady} event.
|
||||
* @event Shard#ready
|
||||
*/
|
||||
this.emit(ShardEvents.Ready);
|
||||
@@ -363,29 +363,18 @@ class Shard extends EventEmitter {
|
||||
if (message._disconnect) {
|
||||
this.ready = false;
|
||||
/**
|
||||
* Emitted upon the shard's {@link Client#event:shardDisconnect} event.
|
||||
* Emitted upon the shard's {@link WebSocketShardEvents#Closed} event.
|
||||
* @event Shard#disconnect
|
||||
*/
|
||||
this.emit(ShardEvents.Disconnect);
|
||||
return;
|
||||
}
|
||||
|
||||
// Shard is attempting to reconnect
|
||||
if (message._reconnecting) {
|
||||
this.ready = false;
|
||||
/**
|
||||
* Emitted upon the shard's {@link Client#event:shardReconnecting} event.
|
||||
* @event Shard#reconnecting
|
||||
*/
|
||||
this.emit(ShardEvents.Reconnecting);
|
||||
return;
|
||||
}
|
||||
|
||||
// Shard has resumed
|
||||
if (message._resume) {
|
||||
this.ready = true;
|
||||
/**
|
||||
* Emitted upon the shard's {@link Client#event:shardResume} event.
|
||||
* Emitted upon the shard's {@link WebSocketShardEvents#Resumed} event.
|
||||
* @event Shard#resume
|
||||
*/
|
||||
this.emit(ShardEvents.Resume);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
const process = require('node:process');
|
||||
const { calculateShardId } = require('@discordjs/util');
|
||||
const { WebSocketShardEvents } = require('@discordjs/ws');
|
||||
const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors');
|
||||
const Events = require('../util/Events');
|
||||
const { makeError, makePlainError } = require('../util/Util');
|
||||
@@ -33,56 +34,32 @@ class ShardClientUtil {
|
||||
switch (mode) {
|
||||
case 'process':
|
||||
process.on('message', this._handleMessage.bind(this));
|
||||
client.on(Events.ShardReady, () => {
|
||||
client.on(Events.ClientReady, () => {
|
||||
process.send({ _ready: true });
|
||||
});
|
||||
client.on(Events.ShardDisconnect, () => {
|
||||
client.ws.on(WebSocketShardEvents.Closed, () => {
|
||||
process.send({ _disconnect: true });
|
||||
});
|
||||
client.on(Events.ShardReconnecting, () => {
|
||||
process.send({ _reconnecting: true });
|
||||
});
|
||||
client.on(Events.ShardResume, () => {
|
||||
client.ws.on(WebSocketShardEvents.Resumed, () => {
|
||||
process.send({ _resume: true });
|
||||
});
|
||||
break;
|
||||
case 'worker':
|
||||
this.parentPort = require('node:worker_threads').parentPort;
|
||||
this.parentPort.on('message', this._handleMessage.bind(this));
|
||||
client.on(Events.ShardReady, () => {
|
||||
client.on(Events.ClientReady, () => {
|
||||
this.parentPort.postMessage({ _ready: true });
|
||||
});
|
||||
client.on(Events.ShardDisconnect, () => {
|
||||
client.ws.on(WebSocketShardEvents.Closed, () => {
|
||||
this.parentPort.postMessage({ _disconnect: true });
|
||||
});
|
||||
client.on(Events.ShardReconnecting, () => {
|
||||
this.parentPort.postMessage({ _reconnecting: true });
|
||||
});
|
||||
client.on(Events.ShardResume, () => {
|
||||
client.ws.on(WebSocketShardEvents.Resumed, () => {
|
||||
this.parentPort.postMessage({ _resume: true });
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Array of shard ids of this client
|
||||
* @type {number[]}
|
||||
* @readonly
|
||||
*/
|
||||
get ids() {
|
||||
return this.client.options.shards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Total number of shards
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get count() {
|
||||
return this.client.options.shardCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the master process.
|
||||
* @param {*} message Message to send
|
||||
|
||||
@@ -16,19 +16,19 @@ class ClientPresence extends Presence {
|
||||
/**
|
||||
* Sets the client's presence
|
||||
* @param {PresenceData} presence The data to set the presence to
|
||||
* @returns {ClientPresence}
|
||||
* @returns {Promise<ClientPresence>}
|
||||
*/
|
||||
set(presence) {
|
||||
async set(presence) {
|
||||
const packet = this._parse(presence);
|
||||
this._patch(packet);
|
||||
if (presence.shardId === undefined) {
|
||||
this.client.ws.broadcast({ op: GatewayOpcodes.PresenceUpdate, d: packet });
|
||||
await this.client._broadcast({ op: GatewayOpcodes.PresenceUpdate, d: packet });
|
||||
} else if (Array.isArray(presence.shardId)) {
|
||||
for (const shardId of presence.shardId) {
|
||||
this.client.ws.shards.get(shardId).send({ op: GatewayOpcodes.PresenceUpdate, d: packet });
|
||||
}
|
||||
await Promise.all(
|
||||
presence.shardId.map(shardId => this.client.ws.send(shardId, { op: GatewayOpcodes.PresenceUpdate, d: packet })),
|
||||
);
|
||||
} else {
|
||||
this.client.ws.shards.get(presence.shardId).send({ op: GatewayOpcodes.PresenceUpdate, d: packet });
|
||||
await this.client.ws.send(presence.shardId, { op: GatewayOpcodes.PresenceUpdate, d: packet });
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ class ClientUser extends User {
|
||||
/**
|
||||
* Sets the full presence of the client user.
|
||||
* @param {PresenceData} data Data for the presence
|
||||
* @returns {ClientPresence}
|
||||
* @returns {Promise<ClientPresence>}
|
||||
* @example
|
||||
* // Set the client user's presence
|
||||
* client.user.setPresence({ activities: [{ name: 'with discord.js' }], status: 'idle' });
|
||||
@@ -157,7 +157,7 @@ class ClientUser extends User {
|
||||
* Sets the status of the client user.
|
||||
* @param {PresenceStatusData} status Status to change to
|
||||
* @param {number|number[]} [shardId] Shard id(s) to have the activity set on
|
||||
* @returns {ClientPresence}
|
||||
* @returns {Promise<ClientPresence>}
|
||||
* @example
|
||||
* // Set the client user's status
|
||||
* client.user.setStatus('idle');
|
||||
@@ -180,7 +180,7 @@ class ClientUser extends User {
|
||||
* Sets the activity the client user is playing.
|
||||
* @param {string|ActivityOptions} name Activity being played, or options for setting the activity
|
||||
* @param {ActivityOptions} [options] Options for setting the activity
|
||||
* @returns {ClientPresence}
|
||||
* @returns {Promise<ClientPresence>}
|
||||
* @example
|
||||
* // Set the client user's activity
|
||||
* client.user.setActivity('discord.js', { type: ActivityType.Watching });
|
||||
@@ -196,7 +196,7 @@ class ClientUser extends User {
|
||||
* Sets/removes the AFK flag for the client user.
|
||||
* @param {boolean} [afk=true] Whether or not the user is AFK
|
||||
* @param {number|number[]} [shardId] Shard Id(s) to have the AFK flag set on
|
||||
* @returns {ClientPresence}
|
||||
* @returns {Promise<ClientPresence>}
|
||||
*/
|
||||
setAFK(afk = true, shardId) {
|
||||
return this.setPresence({ afk, shardId });
|
||||
|
||||
@@ -27,7 +27,6 @@ const RoleManager = require('../managers/RoleManager');
|
||||
const StageInstanceManager = require('../managers/StageInstanceManager');
|
||||
const VoiceStateManager = require('../managers/VoiceStateManager');
|
||||
const { resolveImage } = require('../util/DataResolver');
|
||||
const Status = require('../util/Status');
|
||||
const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField');
|
||||
const { discordSort, getSortableGroupTypes, resolvePartialEmoji } = require('../util/Util');
|
||||
|
||||
@@ -126,15 +125,6 @@ class Guild extends AnonymousGuild {
|
||||
this.shardId = data.shardId;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Shard this Guild belongs to.
|
||||
* @type {WebSocketShard}
|
||||
* @readonly
|
||||
*/
|
||||
get shard() {
|
||||
return this.client.ws.shards.get(this.shardId);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
super._patch(data);
|
||||
this.id = data.id;
|
||||
@@ -1418,8 +1408,7 @@ class Guild extends AnonymousGuild {
|
||||
this.client.voice.adapters.set(this.id, methods);
|
||||
return {
|
||||
sendPayload: data => {
|
||||
if (this.shard.status !== Status.Ready) return false;
|
||||
this.shard.send(data);
|
||||
this.client.ws.send(this.shardId, data);
|
||||
return true;
|
||||
},
|
||||
destroy: () => {
|
||||
|
||||
@@ -325,6 +325,10 @@
|
||||
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GatewayDispatchEvents}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @external GatewayDispatchPayload
|
||||
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10#GatewayDispatchPayload}
|
||||
*/
|
||||
/**
|
||||
* @external GatewayIntentBits
|
||||
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GatewayIntentBits}
|
||||
|
||||
@@ -61,11 +61,6 @@
|
||||
* @property {string} MessageReactionRemoveEmoji messageReactionRemoveEmoji
|
||||
* @property {string} MessageUpdate messageUpdate
|
||||
* @property {string} PresenceUpdate presenceUpdate
|
||||
* @property {string} ShardDisconnect shardDisconnect
|
||||
* @property {string} ShardError shardError
|
||||
* @property {string} ShardReady shardReady
|
||||
* @property {string} ShardReconnecting shardReconnecting
|
||||
* @property {string} ShardResume shardResume
|
||||
* @property {string} StageInstanceCreate stageInstanceCreate
|
||||
* @property {string} StageInstanceDelete stageInstanceDelete
|
||||
* @property {string} StageInstanceUpdate stageInstanceUpdate
|
||||
@@ -99,7 +94,7 @@ module.exports = {
|
||||
ChannelDelete: 'channelDelete',
|
||||
ChannelPinsUpdate: 'channelPinsUpdate',
|
||||
ChannelUpdate: 'channelUpdate',
|
||||
ClientReady: 'ready',
|
||||
ClientReady: 'clientReady',
|
||||
Debug: 'debug',
|
||||
EntitlementCreate: 'entitlementCreate',
|
||||
EntitlementUpdate: 'entitlementUpdate',
|
||||
@@ -148,12 +143,6 @@ module.exports = {
|
||||
MessageReactionRemoveEmoji: 'messageReactionRemoveEmoji',
|
||||
MessageUpdate: 'messageUpdate',
|
||||
PresenceUpdate: 'presenceUpdate',
|
||||
Raw: 'raw',
|
||||
ShardDisconnect: 'shardDisconnect',
|
||||
ShardError: 'shardError',
|
||||
ShardReady: 'shardReady',
|
||||
ShardReconnecting: 'shardReconnecting',
|
||||
ShardResume: 'shardResume',
|
||||
StageInstanceCreate: 'stageInstanceCreate',
|
||||
StageInstanceDelete: 'stageInstanceDelete',
|
||||
StageInstanceUpdate: 'stageInstanceUpdate',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const { DefaultRestOptions, DefaultUserAgentAppendix } = require('@discordjs/rest');
|
||||
const { DefaultWebSocketManagerOptions } = require('@discordjs/ws');
|
||||
const { toSnakeCase } = require('./Transformers');
|
||||
const { version } = require('../../package.json');
|
||||
|
||||
@@ -16,13 +17,8 @@ const { version } = require('../../package.json');
|
||||
/**
|
||||
* Options for a client.
|
||||
* @typedef {Object} ClientOptions
|
||||
* @property {number|number[]|string} [shards] The shard's id to run, or an array of shard ids. If not specified,
|
||||
* the client will spawn {@link ClientOptions#shardCount} shards. If set to `auto`, it will fetch the
|
||||
* recommended amount of shards from Discord and spawn that amount
|
||||
* @property {number} [closeTimeout=5_000] The amount of time in milliseconds to wait for the close frame to be received
|
||||
* from the WebSocket. Don't have this too high/low. It's best to have it between 2_000-6_000 ms.
|
||||
* @property {number} [shardCount=1] The total amount of shards used by all processes of this bot
|
||||
* (e.g. recommended shard count, shard count of the ShardingManager)
|
||||
* @property {CacheFactory} [makeCache] Function to create a cache.
|
||||
* You can use your own function, or the {@link Options} class to customize the Collection used for the cache.
|
||||
* <warn>Overriding the cache used in `GuildManager`, `ChannelManager`, `GuildChannelManager`, `RoleManager`,
|
||||
@@ -33,12 +29,12 @@ const { version } = require('../../package.json');
|
||||
* [guide](https://discordjs.guide/popular-topics/partials.html) for some
|
||||
* important usage information, as partials require you to put checks in place when handling data.
|
||||
* @property {boolean} [failIfNotExists=true] The default value for {@link MessageReplyOptions#failIfNotExists}
|
||||
* @property {PresenceData} [presence={}] Presence data to use upon login
|
||||
* @property {PresenceData} [presence] Presence data to use upon login
|
||||
* @property {IntentsResolvable} intents Intents to enable for this connection
|
||||
* @property {number} [waitGuildTimeout=15_000] Time in milliseconds that clients with the
|
||||
* {@link GatewayIntentBits.Guilds} gateway intent should wait for missing guilds to be received before being ready.
|
||||
* @property {SweeperOptions} [sweepers=this.DefaultSweeperSettings] Options for cache sweeping
|
||||
* @property {WebsocketOptions} [ws] Options for the WebSocket
|
||||
* @property {WebSocketManagerOptions} [ws] Options for the WebSocketManager
|
||||
* @property {RESTOptions} [rest] Options for the REST manager
|
||||
* @property {Function} [jsonTransformer] A function used to transform outgoing json data
|
||||
* @property {boolean} [enforceNonce=false] The default value for {@link MessageReplyOptions#enforceNonce}
|
||||
@@ -60,40 +56,6 @@ const { version } = require('../../package.json');
|
||||
* <info>This property is optional when the key is `invites`, `messages`, or `threads` and `lifetime` is set</info>
|
||||
*/
|
||||
|
||||
/**
|
||||
* A function to determine what strategy to use for sharding internally.
|
||||
* ```js
|
||||
* (manager) => new WorkerShardingStrategy(manager, { shardsPerWorker: 2 })
|
||||
* ```
|
||||
* @typedef {Function} BuildStrategyFunction
|
||||
* @param {WSWebSocketManager} manager The WebSocketManager that is going to initiate the sharding
|
||||
* @returns {IShardingStrategy} The strategy to use for sharding
|
||||
*/
|
||||
|
||||
/**
|
||||
* A function to change the concurrency handling for shard identifies of this manager
|
||||
* ```js
|
||||
* async (manager) => {
|
||||
* const gateway = await manager.fetchGatewayInformation();
|
||||
* return new SimpleIdentifyThrottler(gateway.session_start_limit.max_concurrency);
|
||||
* }
|
||||
* ```
|
||||
* @typedef {Function} IdentifyThrottlerFunction
|
||||
* @param {WSWebSocketManager} manager The WebSocketManager that is going to initiate the sharding
|
||||
* @returns {Awaitable<IIdentifyThrottler>} The identify throttler that this ws manager will use
|
||||
*/
|
||||
|
||||
/**
|
||||
* WebSocket options (these are left as snake_case to match the API)
|
||||
* @typedef {Object} WebsocketOptions
|
||||
* @property {number} [large_threshold=50] Number of members in a guild after which offline users will no longer be
|
||||
* sent in the initial guild member list, must be between 50 and 250
|
||||
* @property {number} [version=10] The Discord gateway version to use <warn>Changing this can break the library;
|
||||
* only set this if you know what you are doing</warn>
|
||||
* @property {BuildStrategyFunction} [buildStrategy] Builds the strategy to use for sharding
|
||||
* @property {IdentifyThrottlerFunction} [buildIdentifyThrottler] Builds the identify throttler to use for sharding
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains various utilities for client options.
|
||||
*/
|
||||
@@ -114,15 +76,14 @@ class Options extends null {
|
||||
return {
|
||||
closeTimeout: 5_000,
|
||||
waitGuildTimeout: 15_000,
|
||||
shardCount: 1,
|
||||
makeCache: this.cacheWithLimits(this.DefaultMakeCacheSettings),
|
||||
partials: [],
|
||||
failIfNotExists: true,
|
||||
enforceNonce: false,
|
||||
presence: {},
|
||||
sweepers: this.DefaultSweeperSettings,
|
||||
ws: {
|
||||
large_threshold: 50,
|
||||
...DefaultWebSocketManagerOptions,
|
||||
largeThreshold: 50,
|
||||
version: 10,
|
||||
},
|
||||
rest: {
|
||||
@@ -224,7 +185,7 @@ module.exports = Options;
|
||||
*/
|
||||
|
||||
/**
|
||||
* @external WSWebSocketManager
|
||||
* @external WebSocketManager
|
||||
* @see {@link https://discord.js.org/docs/packages/ws/stable/WebSocketManager:Class}
|
||||
*/
|
||||
|
||||
|
||||
@@ -5,14 +5,8 @@ const { createEnum } = require('./Enums');
|
||||
/**
|
||||
* @typedef {Object} Status
|
||||
* @property {number} Ready
|
||||
* @property {number} Connecting
|
||||
* @property {number} Reconnecting
|
||||
* @property {number} Idle
|
||||
* @property {number} Nearly
|
||||
* @property {number} Disconnected
|
||||
* @property {number} WaitingForGuilds
|
||||
* @property {number} Identifying
|
||||
* @property {number} Resuming
|
||||
*/
|
||||
|
||||
// JSDoc for IntelliSense purposes
|
||||
@@ -20,14 +14,4 @@ const { createEnum } = require('./Enums');
|
||||
* @type {Status}
|
||||
* @ignore
|
||||
*/
|
||||
module.exports = createEnum([
|
||||
'Ready',
|
||||
'Connecting',
|
||||
'Reconnecting',
|
||||
'Idle',
|
||||
'Nearly',
|
||||
'Disconnected',
|
||||
'WaitingForGuilds',
|
||||
'Identifying',
|
||||
'Resuming',
|
||||
]);
|
||||
module.exports = createEnum(['Ready', 'Idle', 'WaitingForGuilds']);
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @typedef {Object} WebSocketShardEvents
|
||||
* @property {string} Close close
|
||||
* @property {string} Destroyed destroyed
|
||||
* @property {string} InvalidSession invalidSession
|
||||
* @property {string} Ready ready
|
||||
* @property {string} Resumed resumed
|
||||
* @property {string} AllReady allReady
|
||||
*/
|
||||
|
||||
// JSDoc for IntelliSense purposes
|
||||
/**
|
||||
* @type {WebSocketShardEvents}
|
||||
* @ignore
|
||||
*/
|
||||
module.exports = {
|
||||
Close: 'close',
|
||||
Destroyed: 'destroyed',
|
||||
InvalidSession: 'invalidSession',
|
||||
Ready: 'ready',
|
||||
Resumed: 'resumed',
|
||||
AllReady: 'allReady',
|
||||
};
|
||||
124
packages/discord.js/typings/index.d.ts
vendored
124
packages/discord.js/typings/index.d.ts
vendored
@@ -20,12 +20,7 @@ import {
|
||||
import { Awaitable, JSONEncodable } from '@discordjs/util';
|
||||
import { Collection, ReadonlyCollection } from '@discordjs/collection';
|
||||
import { BaseImageURLOptions, ImageURLOptions, RawFile, REST, RESTOptions } from '@discordjs/rest';
|
||||
import {
|
||||
WebSocketManager as WSWebSocketManager,
|
||||
IShardingStrategy,
|
||||
IIdentifyThrottler,
|
||||
SessionInfo,
|
||||
} from '@discordjs/ws';
|
||||
import { WebSocketManager, WebSocketManagerOptions } from '@discordjs/ws';
|
||||
import {
|
||||
APIActionRowComponent,
|
||||
APIApplicationCommandInteractionData,
|
||||
@@ -50,7 +45,6 @@ import {
|
||||
ButtonStyle,
|
||||
ChannelType,
|
||||
ComponentType,
|
||||
GatewayDispatchEvents,
|
||||
GatewayVoiceServerUpdateDispatchData,
|
||||
GatewayVoiceStateUpdateDispatchData,
|
||||
GuildFeature,
|
||||
@@ -170,6 +164,8 @@ import {
|
||||
GuildScheduledEventRecurrenceRuleWeekday,
|
||||
GuildScheduledEventRecurrenceRuleMonth,
|
||||
GuildScheduledEventRecurrenceRuleFrequency,
|
||||
GatewaySendPayload,
|
||||
GatewayDispatchPayload,
|
||||
} from 'discord-api-types/v10';
|
||||
import { ChildProcess } from 'node:child_process';
|
||||
import { EventEmitter } from 'node:events';
|
||||
@@ -956,8 +952,16 @@ export type If<Value extends boolean, TrueResult, FalseResult = null> = Value ex
|
||||
export class Client<Ready extends boolean = boolean> extends BaseClient {
|
||||
public constructor(options: ClientOptions);
|
||||
private actions: unknown;
|
||||
private expectedGuilds: Set<Snowflake>;
|
||||
private readonly packetQueue: unknown[];
|
||||
private presence: ClientPresence;
|
||||
private pings: Collection<number, number>;
|
||||
private readyTimeout: NodeJS.Timeout | null;
|
||||
private _broadcast(packet: GatewaySendPayload): void;
|
||||
private _eval(script: string): unknown;
|
||||
private _handlePacket(packet?: GatewayDispatchPayload, shardId?: number): boolean;
|
||||
private _checkReady(): void;
|
||||
private _triggerClientReady(): void;
|
||||
private _validateOptions(options: ClientOptions): void;
|
||||
private get _censoredToken(): string | null;
|
||||
// This a technique used to brand the ready state. Or else we'll get `never` errors on typeguard checks.
|
||||
@@ -979,17 +983,21 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
|
||||
public channels: ChannelManager;
|
||||
public get emojis(): BaseGuildEmojiManager;
|
||||
public guilds: GuildManager;
|
||||
public lastPingTimestamp: number;
|
||||
public options: Omit<ClientOptions, 'intents'> & { intents: IntentsBitField };
|
||||
public get ping(): number;
|
||||
public get readyAt(): If<Ready, Date>;
|
||||
public readyTimestamp: If<Ready, number>;
|
||||
public sweepers: Sweepers;
|
||||
public shard: ShardClientUtil | null;
|
||||
public status: Status;
|
||||
public token: If<Ready, string, string | null>;
|
||||
public get uptime(): If<Ready, number>;
|
||||
public user: If<Ready, ClientUser>;
|
||||
public users: UserManager;
|
||||
public voice: ClientVoiceManager;
|
||||
public ws: WebSocketManager;
|
||||
|
||||
public destroy(): Promise<void>;
|
||||
public deleteWebhook(id: Snowflake, options?: WebhookDeleteOptions): Promise<void>;
|
||||
public fetchGuildPreview(guild: GuildResolvable): Promise<GuildPreview>;
|
||||
@@ -1431,7 +1439,6 @@ export class Guild extends AnonymousGuild {
|
||||
public get safetyAlertsChannel(): TextChannel | null;
|
||||
public safetyAlertsChannelId: Snowflake | null;
|
||||
public scheduledEvents: GuildScheduledEventManager;
|
||||
public get shard(): WebSocketShard;
|
||||
public shardId: number;
|
||||
public stageInstances: StageInstanceManager;
|
||||
public stickers: GuildStickerManager;
|
||||
@@ -3632,70 +3639,6 @@ export class WebhookClient extends BaseClient {
|
||||
public send(options: string | MessagePayload | WebhookMessageCreateOptions): Promise<APIMessage>;
|
||||
}
|
||||
|
||||
export class WebSocketManager extends EventEmitter {
|
||||
private constructor(client: Client);
|
||||
private readonly packetQueue: unknown[];
|
||||
private destroyed: boolean;
|
||||
|
||||
public readonly client: Client;
|
||||
public gateway: string | null;
|
||||
public shards: Collection<number, WebSocketShard>;
|
||||
public status: Status;
|
||||
public get ping(): number;
|
||||
|
||||
public on(event: GatewayDispatchEvents, listener: (data: any, shardId: number) => void): this;
|
||||
public once(event: GatewayDispatchEvents, listener: (data: any, shardId: number) => void): this;
|
||||
|
||||
private debug(messages: readonly string[], shardId?: number): void;
|
||||
private connect(): Promise<void>;
|
||||
private broadcast(packet: unknown): void;
|
||||
private destroy(): Promise<void>;
|
||||
private handlePacket(packet?: unknown, shard?: WebSocketShard): boolean;
|
||||
private checkShardsReady(): void;
|
||||
private triggerClientReady(): void;
|
||||
}
|
||||
|
||||
export interface WebSocketShardEventTypes {
|
||||
ready: [];
|
||||
resumed: [];
|
||||
invalidSession: [];
|
||||
destroyed: [];
|
||||
close: [event: CloseEvent];
|
||||
allReady: [unavailableGuilds?: Set<Snowflake>];
|
||||
}
|
||||
|
||||
export class WebSocketShard extends EventEmitter {
|
||||
private constructor(manager: WebSocketManager, id: number);
|
||||
private closeSequence: number;
|
||||
private sessionInfo: SessionInfo | null;
|
||||
public lastPingTimestamp: number;
|
||||
private expectedGuilds: Set<Snowflake> | null;
|
||||
private readyTimeout: NodeJS.Timeout | null;
|
||||
|
||||
public manager: WebSocketManager;
|
||||
public id: number;
|
||||
public status: Status;
|
||||
public ping: number;
|
||||
|
||||
private debug(messages: readonly string[]): void;
|
||||
private onReadyPacket(packet: unknown): void;
|
||||
private gotGuild(guildId: Snowflake): void;
|
||||
private checkReady(): void;
|
||||
private emitClose(event?: CloseEvent): void;
|
||||
|
||||
public send(data: unknown, important?: boolean): void;
|
||||
|
||||
public on<Event extends keyof WebSocketShardEventTypes>(
|
||||
event: Event,
|
||||
listener: (...args: WebSocketShardEventTypes[Event]) => void,
|
||||
): this;
|
||||
|
||||
public once<Event extends keyof WebSocketShardEventTypes>(
|
||||
event: Event,
|
||||
listener: (...args: WebSocketShardEventTypes[Event]) => void,
|
||||
): this;
|
||||
}
|
||||
|
||||
export class Widget extends Base {
|
||||
private constructor(client: Client<true>, data: RawWidgetData);
|
||||
private _patch(data: RawWidgetData): void;
|
||||
@@ -5133,6 +5076,7 @@ export interface ClientEvents {
|
||||
oldChannel: DMChannel | NonThreadGuildBasedChannel,
|
||||
newChannel: DMChannel | NonThreadGuildBasedChannel,
|
||||
];
|
||||
clientReady: [client: Client<true>];
|
||||
debug: [message: string];
|
||||
warn: [message: string];
|
||||
emojiCreate: [emoji: GuildEmoji];
|
||||
@@ -5186,7 +5130,6 @@ export interface ClientEvents {
|
||||
newMessage: OmitPartialGroupDMChannel<Message>,
|
||||
];
|
||||
presenceUpdate: [oldPresence: Presence | null, newPresence: Presence];
|
||||
ready: [client: Client<true>];
|
||||
invalidated: [];
|
||||
roleCreate: [role: Role];
|
||||
roleDelete: [role: Role];
|
||||
@@ -5206,11 +5149,6 @@ export interface ClientEvents {
|
||||
voiceStateUpdate: [oldState: VoiceState, newState: VoiceState];
|
||||
webhooksUpdate: [channel: TextChannel | NewsChannel | VoiceChannel | ForumChannel | MediaChannel];
|
||||
interactionCreate: [interaction: Interaction];
|
||||
shardDisconnect: [closeEvent: CloseEvent, shardId: number];
|
||||
shardError: [error: Error, shardId: number];
|
||||
shardReady: [shardId: number, unavailableGuilds: Set<Snowflake> | undefined];
|
||||
shardReconnecting: [shardId: number];
|
||||
shardResume: [shardId: number, replayedEvents: number];
|
||||
stageInstanceCreate: [stageInstance: StageInstance];
|
||||
stageInstanceUpdate: [oldStageInstance: StageInstance | null, newStageInstance: StageInstance];
|
||||
stageInstanceDelete: [stageInstance: StageInstance];
|
||||
@@ -5232,8 +5170,6 @@ export interface ClientFetchInviteOptions {
|
||||
}
|
||||
|
||||
export interface ClientOptions {
|
||||
shards?: number | readonly number[] | 'auto';
|
||||
shardCount?: number;
|
||||
closeTimeout?: number;
|
||||
makeCache?: CacheFactory;
|
||||
allowedMentions?: MessageMentionOptions;
|
||||
@@ -5243,7 +5179,7 @@ export interface ClientOptions {
|
||||
intents: BitFieldResolvable<GatewayIntentsString, number>;
|
||||
waitGuildTimeout?: number;
|
||||
sweepers?: SweeperOptions;
|
||||
ws?: WebSocketOptions;
|
||||
ws?: Partial<WebSocketManagerOptions>;
|
||||
rest?: Partial<RESTOptions>;
|
||||
jsonTransformer?: (obj: unknown) => unknown;
|
||||
enforceNonce?: boolean;
|
||||
@@ -5263,14 +5199,6 @@ export interface ClientUserEditOptions {
|
||||
banner?: BufferResolvable | Base64Resolvable | null;
|
||||
}
|
||||
|
||||
export interface CloseEvent {
|
||||
/** @deprecated Not used anymore since using {@link @discordjs/ws#(WebSocketManager:class)} internally */
|
||||
wasClean: boolean;
|
||||
code: number;
|
||||
/** @deprecated Not used anymore since using {@link @discordjs/ws#(WebSocketManager:class)} internally */
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export type CollectorFilter<Arguments extends unknown[]> = (...args: Arguments) => Awaitable<boolean>;
|
||||
|
||||
export interface CollectorOptions<FilterArguments extends unknown[]> {
|
||||
@@ -5364,7 +5292,7 @@ export enum Events {
|
||||
AutoModerationRuleCreate = 'autoModerationRuleCreate',
|
||||
AutoModerationRuleDelete = 'autoModerationRuleDelete',
|
||||
AutoModerationRuleUpdate = 'autoModerationRuleUpdate',
|
||||
ClientReady = 'ready',
|
||||
ClientReady = 'clientReady',
|
||||
EntitlementCreate = 'entitlementCreate',
|
||||
EntitlementDelete = 'entitlementDelete',
|
||||
EntitlementUpdate = 'entitlementUpdate',
|
||||
@@ -5452,15 +5380,6 @@ export enum ShardEvents {
|
||||
Spawn = 'spawn',
|
||||
}
|
||||
|
||||
export enum WebSocketShardEvents {
|
||||
Close = 'close',
|
||||
Destroyed = 'destroyed',
|
||||
InvalidSession = 'invalidSession',
|
||||
Ready = 'ready',
|
||||
Resumed = 'resumed',
|
||||
AllReady = 'allReady',
|
||||
}
|
||||
|
||||
export enum Status {
|
||||
Ready = 0,
|
||||
Connecting = 1,
|
||||
@@ -6879,13 +6798,6 @@ export interface WebhookMessageCreateOptions extends Omit<MessageCreateOptions,
|
||||
appliedTags?: readonly Snowflake[];
|
||||
}
|
||||
|
||||
export interface WebSocketOptions {
|
||||
large_threshold?: number;
|
||||
version?: number;
|
||||
buildStrategy?(manager: WSWebSocketManager): IShardingStrategy;
|
||||
buildIdentifyThrottler?(manager: WSWebSocketManager): Awaitable<IIdentifyThrottler>;
|
||||
}
|
||||
|
||||
export interface WidgetActivity {
|
||||
name: string;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ import {
|
||||
Client,
|
||||
ClientApplication,
|
||||
ClientUser,
|
||||
CloseEvent,
|
||||
Collection,
|
||||
ChatInputCommandInteraction,
|
||||
CommandInteractionOption,
|
||||
@@ -100,7 +99,6 @@ import {
|
||||
User,
|
||||
VoiceChannel,
|
||||
Shard,
|
||||
WebSocketShard,
|
||||
Collector,
|
||||
GuildAuditLogsEntry,
|
||||
GuildAuditLogs,
|
||||
@@ -112,7 +110,6 @@ import {
|
||||
RepliableInteraction,
|
||||
ThreadChannelType,
|
||||
Events,
|
||||
WebSocketShardEvents,
|
||||
Status,
|
||||
CategoryChannelChildManager,
|
||||
ActionRowData,
|
||||
@@ -677,7 +674,7 @@ client.on('presenceUpdate', (oldPresence, { client }) => {
|
||||
declare const slashCommandBuilder: SlashCommandBuilder;
|
||||
declare const contextMenuCommandBuilder: ContextMenuCommandBuilder;
|
||||
|
||||
client.on('ready', async client => {
|
||||
client.on('clientReady', async client => {
|
||||
expectType<Client<true>>(client);
|
||||
console.log(`Client is logged in as ${client.user.tag} and ready!`);
|
||||
|
||||
@@ -1305,8 +1302,8 @@ client.on('guildCreate', async g => {
|
||||
});
|
||||
|
||||
// EventEmitter static method overrides
|
||||
expectType<Promise<[Client<true>]>>(Client.once(client, 'ready'));
|
||||
expectType<AsyncIterableIterator<[Client<true>]>>(Client.on(client, 'ready'));
|
||||
expectType<Promise<[Client<true>]>>(Client.once(client, 'clientReady'));
|
||||
expectType<AsyncIterableIterator<[Client<true>]>>(Client.on(client, 'clientReady'));
|
||||
|
||||
client.login('absolutely-valid-token');
|
||||
|
||||
@@ -1426,7 +1423,6 @@ reactionCollector.on('dispose', (...args) => {
|
||||
// Make sure the properties are typed correctly, and that no backwards properties
|
||||
// (K -> V and V -> K) exist:
|
||||
expectAssignable<'messageCreate'>(Events.MessageCreate);
|
||||
expectAssignable<'close'>(WebSocketShardEvents.Close);
|
||||
expectAssignable<'death'>(ShardEvents.Death);
|
||||
expectAssignable<1>(Status.Connecting);
|
||||
|
||||
@@ -2100,12 +2096,6 @@ shard.on('death', process => {
|
||||
expectType<ChildProcess | Worker>(process);
|
||||
});
|
||||
|
||||
declare const webSocketShard: WebSocketShard;
|
||||
|
||||
webSocketShard.on('close', event => {
|
||||
expectType<CloseEvent>(event);
|
||||
});
|
||||
|
||||
declare const collector: Collector<string, Interaction, string[]>;
|
||||
|
||||
collector.on('collect', (collected, ...other) => {
|
||||
|
||||
62
pnpm-lock.yaml
generated
62
pnpm-lock.yaml
generated
@@ -932,8 +932,8 @@ importers:
|
||||
specifier: workspace:^
|
||||
version: link:../util
|
||||
'@discordjs/ws':
|
||||
specifier: 1.1.1
|
||||
version: 1.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
|
||||
specifier: workspace:^
|
||||
version: link:../ws
|
||||
'@sapphire/snowflake':
|
||||
specifier: 3.5.3
|
||||
version: 3.5.3
|
||||
@@ -2609,10 +2609,6 @@ packages:
|
||||
resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
|
||||
'@discordjs/collection@2.1.0':
|
||||
resolution: {integrity: sha512-mLcTACtXUuVgutoznkh6hS3UFqYirDYAg5Dc1m8xn6OvPjetnUlf/xjtqnnc47OwWdaoCQnHmHh9KofhD6uRqw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@discordjs/formatters@0.5.0':
|
||||
resolution: {integrity: sha512-98b3i+Y19RFq1Xke4NkVY46x8KjJQjldHUuEbCqMvp1F5Iq9HgnGpu91jOi/Ufazhty32eRsKnnzS8n4c+L93g==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2625,22 +2621,10 @@ packages:
|
||||
resolution: {integrity: sha512-NEE76A96FtQ5YuoAVlOlB3ryMPrkXbUCTQICHGKb8ShtjXyubGicjRMouHtP1RpuDdm16cDa+oI3aAMo1zQRUQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
'@discordjs/rest@2.3.0':
|
||||
resolution: {integrity: sha512-C1kAJK8aSYRv3ZwMG8cvrrW4GN0g5eMdP8AuN8ODH5DyOCbHgJspze1my3xHOAgwLJdKUbWNVyAeJ9cEdduqIg==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
|
||||
'@discordjs/util@1.1.0':
|
||||
resolution: {integrity: sha512-IndcI5hzlNZ7GS96RV3Xw1R2kaDuXEp7tRIy/KlhidpN/BQ1qh1NZt3377dMLTa44xDUNKT7hnXkA/oUAzD/lg==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
|
||||
'@discordjs/util@1.1.1':
|
||||
resolution: {integrity: sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@discordjs/ws@1.1.1':
|
||||
resolution: {integrity: sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==}
|
||||
engines: {node: '>=16.11.0'}
|
||||
|
||||
'@edge-runtime/format@2.2.1':
|
||||
resolution: {integrity: sha512-JQTRVuiusQLNNLe2W9tnzBlV/GvSVcozLl4XZHk5swnRZ/v6jp8TqR8P7sqmJsQqblDZ3EztcWmLDbhRje/+8g==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -7631,9 +7615,6 @@ packages:
|
||||
discord-api-types@0.37.101:
|
||||
resolution: {integrity: sha512-2wizd94t7G3A8U5Phr3AiuL4gSvhqistDwWnlk1VLTit8BI1jWUncFqFQNdPbHqS3661+Nx/iEyIwtVjPuBP3w==}
|
||||
|
||||
discord-api-types@0.37.83:
|
||||
resolution: {integrity: sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==}
|
||||
|
||||
discord-api-types@0.37.97:
|
||||
resolution: {integrity: sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA==}
|
||||
|
||||
@@ -13035,10 +13016,6 @@ packages:
|
||||
resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==}
|
||||
engines: {node: '>=14.0'}
|
||||
|
||||
undici@6.13.0:
|
||||
resolution: {integrity: sha512-Q2rtqmZWrbP8nePMq7mOJIN98M0fYvSgV89vwl/BQRT4mDOeY2GXZngfGpcBBhtky3woM7G24wZV3Q304Bv6cw==}
|
||||
engines: {node: '>=18.0'}
|
||||
|
||||
undici@6.19.8:
|
||||
resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==}
|
||||
engines: {node: '>=18.17'}
|
||||
@@ -14857,8 +14834,6 @@ snapshots:
|
||||
|
||||
'@discordjs/collection@1.5.3': {}
|
||||
|
||||
'@discordjs/collection@2.1.0': {}
|
||||
|
||||
'@discordjs/formatters@0.5.0':
|
||||
dependencies:
|
||||
discord-api-types: 0.37.97
|
||||
@@ -14886,37 +14861,8 @@ snapshots:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@discordjs/rest@2.3.0':
|
||||
dependencies:
|
||||
'@discordjs/collection': 2.1.0
|
||||
'@discordjs/util': 1.1.0
|
||||
'@sapphire/async-queue': 1.5.3
|
||||
'@sapphire/snowflake': 3.5.3
|
||||
'@vladfrangu/async_event_emitter': 2.4.6
|
||||
discord-api-types: 0.37.83
|
||||
magic-bytes.js: 1.10.0
|
||||
tslib: 2.6.3
|
||||
undici: 6.13.0
|
||||
|
||||
'@discordjs/util@1.1.0': {}
|
||||
|
||||
'@discordjs/util@1.1.1': {}
|
||||
|
||||
'@discordjs/ws@1.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)':
|
||||
dependencies:
|
||||
'@discordjs/collection': 2.1.0
|
||||
'@discordjs/rest': 2.3.0
|
||||
'@discordjs/util': 1.1.0
|
||||
'@sapphire/async-queue': 1.5.3
|
||||
'@types/ws': 8.5.12
|
||||
'@vladfrangu/async_event_emitter': 2.4.6
|
||||
discord-api-types: 0.37.83
|
||||
tslib: 2.6.3
|
||||
ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
'@edge-runtime/format@2.2.1': {}
|
||||
|
||||
'@edge-runtime/node-utils@2.3.0': {}
|
||||
@@ -21518,8 +21464,6 @@ snapshots:
|
||||
|
||||
discord-api-types@0.37.101: {}
|
||||
|
||||
discord-api-types@0.37.83: {}
|
||||
|
||||
discord-api-types@0.37.97: {}
|
||||
|
||||
dlv@1.1.3: {}
|
||||
@@ -28791,8 +28735,6 @@ snapshots:
|
||||
dependencies:
|
||||
'@fastify/busboy': 2.1.1
|
||||
|
||||
undici@6.13.0: {}
|
||||
|
||||
undici@6.19.8: {}
|
||||
|
||||
unicode-canonical-property-names-ecmascript@2.0.0: {}
|
||||
|
||||
Reference in New Issue
Block a user