discord.js 14.24.1 → 14.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "discord.js",
4
- "version": "14.24.1",
4
+ "version": "14.25.0",
5
5
  "description": "A powerful library for interacting with the Discord API",
6
6
  "main": "./src/index.js",
7
7
  "types": "./typings/index.d.ts",
@@ -53,19 +53,19 @@
53
53
  "homepage": "https://discord.js.org",
54
54
  "funding": "https://github.com/discordjs/discord.js?sponsor",
55
55
  "dependencies": {
56
- "@discordjs/builders": "^1.13.0",
57
56
  "@discordjs/collection": "1.5.3",
58
- "@discordjs/formatters": "^0.6.1",
59
57
  "@discordjs/ws": "^1.2.3",
60
58
  "@sapphire/snowflake": "3.5.3",
61
- "discord-api-types": "^0.38.31",
59
+ "discord-api-types": "^0.38.33",
62
60
  "fast-deep-equal": "3.1.3",
63
61
  "lodash.snakecase": "4.1.1",
64
62
  "magic-bytes.js": "^1.10.0",
65
63
  "tslib": "^2.6.3",
66
64
  "undici": "6.21.3",
65
+ "@discordjs/builders": "^1.13.0",
66
+ "@discordjs/formatters": "^0.6.2",
67
67
  "@discordjs/rest": "^2.6.0",
68
- "@discordjs/util": "^1.1.1"
68
+ "@discordjs/util": "^1.2.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@favware/cliff-jumper": "^4.1.0",
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ const process = require('node:process');
4
+ const { GatewayOpcodes } = require('discord-api-types/v10');
5
+
6
+ const emittedFor = new Set();
7
+
8
+ module.exports = (_, { d: data }) => {
9
+ switch (data.opcode) {
10
+ case GatewayOpcodes.RequestGuildMembers: {
11
+ break;
12
+ }
13
+
14
+ default: {
15
+ if (!emittedFor.has(data.opcode)) {
16
+ process.emitWarning(
17
+ // eslint-disable-next-line max-len
18
+ `Hit a gateway rate limit on opcode ${data.opcode} (${GatewayOpcodes[data.opcode]}). If the discord.js version you're using is up-to-date, please open an issue on GitHub.`,
19
+ );
20
+
21
+ emittedFor.add(data.opcode);
22
+ }
23
+ }
24
+ }
25
+ };
@@ -52,6 +52,7 @@ const handlers = Object.fromEntries([
52
52
  ['MESSAGE_REACTION_REMOVE_EMOJI', require('./MESSAGE_REACTION_REMOVE_EMOJI')],
53
53
  ['MESSAGE_UPDATE', require('./MESSAGE_UPDATE')],
54
54
  ['PRESENCE_UPDATE', require('./PRESENCE_UPDATE')],
55
+ ['RATE_LIMITED', require('./RATE_LIMITED')],
55
56
  ['READY', require('./READY')],
56
57
  ['RESUMED', require('./RESUMED')],
57
58
  ['SOUNDBOARD_SOUNDS', require('./SOUNDBOARD_SOUNDS')],
@@ -120,12 +120,12 @@ class GuildBanManager extends CachedManager {
120
120
  return this._add(data, cache);
121
121
  }
122
122
 
123
- async _fetchMany(options = {}) {
123
+ async _fetchMany({ cache, ...apiOptions } = {}) {
124
124
  const data = await this.client.rest.get(Routes.guildBans(this.guild.id), {
125
- query: makeURLSearchParams(options),
125
+ query: makeURLSearchParams(apiOptions),
126
126
  });
127
127
 
128
- return data.reduce((col, ban) => col.set(ban.user.id, this._add(ban, options.cache)), new Collection());
128
+ return data.reduce((col, ban) => col.set(ban.user.id, this._add(ban, cache)), new Collection());
129
129
  }
130
130
 
131
131
  /**
@@ -31,7 +31,15 @@ class GuildEmojiRoleManager extends DataManager {
31
31
  * @readonly
32
32
  */
33
33
  get cache() {
34
- return this.guild.roles.cache.filter(role => this.emoji._roles.includes(role.id));
34
+ const cache = new Collection();
35
+ for (const roleId of this.emoji._roles) {
36
+ const role = this.guild.roles.cache.get(roleId);
37
+ if (role !== undefined) {
38
+ cache.set(roleId, role);
39
+ }
40
+ }
41
+
42
+ return cache;
35
43
  }
36
44
 
37
45
  /**
@@ -4,8 +4,9 @@ const { process } = require('node:process');
4
4
  const { setTimeout, clearTimeout } = require('node:timers');
5
5
  const { Collection } = require('@discordjs/collection');
6
6
  const { makeURLSearchParams } = require('@discordjs/rest');
7
+ const { GatewayRateLimitError } = require('@discordjs/util');
7
8
  const { DiscordSnowflake } = require('@sapphire/snowflake');
8
- const { Routes, GatewayOpcodes } = require('discord-api-types/v10');
9
+ const { Routes, GatewayOpcodes, GatewayDispatchEvents } = require('discord-api-types/v10');
9
10
  const CachedManager = require('./CachedManager');
10
11
  const { DiscordjsError, DiscordjsTypeError, DiscordjsRangeError, ErrorCodes } = require('../errors');
11
12
  const BaseGuildVoiceChannel = require('../structures/BaseGuildVoiceChannel');
@@ -239,19 +240,25 @@ class GuildMemberManager extends CachedManager {
239
240
 
240
241
  return new Promise((resolve, reject) => {
241
242
  if (!query && !users) query = '';
242
- this.guild.shard.send({
243
- op: GatewayOpcodes.RequestGuildMembers,
244
- d: {
245
- guild_id: this.guild.id,
246
- presences,
247
- user_ids: users,
248
- query,
249
- nonce,
250
- limit,
251
- },
252
- });
253
243
  const fetchedMembers = new Collection();
254
244
  let i = 0;
245
+
246
+ const cleanup = () => {
247
+ /* eslint-disable no-use-before-define */
248
+ clearTimeout(timeout);
249
+
250
+ this.client.removeListener(Events.Raw, rateLimitHandler);
251
+ this.client.decrementMaxListeners();
252
+ this.client.removeListener(Events.GuildMembersChunk, handler);
253
+ this.client.decrementMaxListeners();
254
+ /* eslint-enable no-use-before-define */
255
+ };
256
+
257
+ const timeout = setTimeout(() => {
258
+ cleanup();
259
+ reject(new DiscordjsError(ErrorCodes.GuildMembersTimeout));
260
+ }, time).unref();
261
+
255
262
  const handler = (members, _, chunk) => {
256
263
  if (chunk.nonce !== nonce) return;
257
264
  timeout.refresh();
@@ -260,19 +267,37 @@ class GuildMemberManager extends CachedManager {
260
267
  fetchedMembers.set(member.id, member);
261
268
  }
262
269
  if (members.size < 1_000 || (limit && fetchedMembers.size >= limit) || i === chunk.count) {
263
- clearTimeout(timeout);
264
- this.client.removeListener(Events.GuildMembersChunk, handler);
265
- this.client.decrementMaxListeners();
270
+ cleanup();
266
271
  resolve(users && !Array.isArray(users) && fetchedMembers.size ? fetchedMembers.first() : fetchedMembers);
267
272
  }
268
273
  };
269
- const timeout = setTimeout(() => {
270
- this.client.removeListener(Events.GuildMembersChunk, handler);
271
- this.client.decrementMaxListeners();
272
- reject(new DiscordjsError(ErrorCodes.GuildMembersTimeout));
273
- }, time).unref();
274
+
275
+ const requestData = {
276
+ guild_id: this.guild.id,
277
+ presences,
278
+ user_ids: users,
279
+ query,
280
+ nonce,
281
+ limit,
282
+ };
283
+
284
+ const rateLimitHandler = payload => {
285
+ if (payload.t === GatewayDispatchEvents.RateLimited && payload.d.meta.nonce === nonce) {
286
+ cleanup();
287
+ reject(new GatewayRateLimitError(payload.d, requestData));
288
+ }
289
+ };
290
+
291
+ this.client.incrementMaxListeners();
292
+ this.client.on(Events.Raw, rateLimitHandler);
293
+
274
294
  this.client.incrementMaxListeners();
275
295
  this.client.on(Events.GuildMembersChunk, handler);
296
+
297
+ this.guild.shard.send({
298
+ op: GatewayOpcodes.RequestGuildMembers,
299
+ d: requestData,
300
+ });
276
301
  });
277
302
  }
278
303
 
@@ -33,8 +33,17 @@ class GuildMemberRoleManager extends DataManager {
33
33
  * @readonly
34
34
  */
35
35
  get cache() {
36
- const everyone = this.guild.roles.everyone;
37
- return this.guild.roles.cache.filter(role => this.member._roles.includes(role.id)).set(everyone.id, everyone);
36
+ const cache = new Collection();
37
+ cache.set(this.guild.id, this.guild.roles.everyone);
38
+
39
+ for (const roleId of this.member._roles) {
40
+ const role = this.guild.roles.cache.get(roleId);
41
+ if (role !== undefined) {
42
+ cache.set(roleId, role);
43
+ }
44
+ }
45
+
46
+ return cache;
38
47
  }
39
48
 
40
49
  /**
@@ -111,12 +111,12 @@ class MessageManager extends CachedManager {
111
111
  return this._add(data, cache);
112
112
  }
113
113
 
114
- async _fetchMany(options = {}) {
114
+ async _fetchMany({ cache, ...apiOptions } = {}) {
115
115
  const data = await this.client.rest.get(Routes.channelMessages(this.channel.id), {
116
- query: makeURLSearchParams(options),
116
+ query: makeURLSearchParams(apiOptions),
117
117
  });
118
118
 
119
- return data.reduce((_data, message) => _data.set(message.id, this._add(message, options.cache)), new Collection());
119
+ return data.reduce((_data, message) => _data.set(message.id, this._add(message, cache)), new Collection());
120
120
  }
121
121
 
122
122
  /**
@@ -146,8 +146,8 @@ class MessageManager extends CachedManager {
146
146
  */
147
147
 
148
148
  /**
149
- * Fetches the pinned messages of this channel and returns a collection of them.
150
- * <info>The returned Collection does not contain any reaction data of the messages.
149
+ * Fetches the pinned messages of this channel, returning a paginated result.
150
+ * <info>The returned messages do not contain any reaction data.
151
151
  * Those need to be fetched separately.</info>
152
152
  *
153
153
  * @param {FetchPinnedMessagesOptions} [options={}] Options for fetching pinned messages
@@ -158,11 +158,11 @@ class MessageManager extends CachedManager {
158
158
  * .then(messages => console.log(`Received ${messages.items.length} messages`))
159
159
  * .catch(console.error);
160
160
  */
161
- async fetchPins(options = {}) {
161
+ async fetchPins({ cache, ...apiOptions } = {}) {
162
162
  const data = await this.client.rest.get(Routes.channelMessagesPins(this.channel.id), {
163
163
  query: makeURLSearchParams({
164
- ...options,
165
- before: options.before && new Date(options.before).toISOString(),
164
+ ...apiOptions,
165
+ before: apiOptions.before && new Date(apiOptions.before).toISOString(),
166
166
  }),
167
167
  });
168
168
 
@@ -172,7 +172,7 @@ class MessageManager extends CachedManager {
172
172
  get pinnedAt() {
173
173
  return new Date(this.pinnedTimestamp);
174
174
  },
175
- message: this._add(item.message, options.cache),
175
+ message: this._add(item.message, cache),
176
176
  })),
177
177
  hasMore: data.has_more,
178
178
  };
@@ -24,12 +24,6 @@ class GuildMember extends Base {
24
24
  */
25
25
  this.guild = guild;
26
26
 
27
- /**
28
- * The timestamp the member joined the guild at
29
- * @type {?number}
30
- */
31
- this.joinedTimestamp = null;
32
-
33
27
  /**
34
28
  * The last timestamp this member started boosting the guild
35
29
  * @type {?number}
@@ -62,7 +56,7 @@ class GuildMember extends Base {
62
56
  */
63
57
  Object.defineProperty(this, '_roles', { value: [], writable: true });
64
58
 
65
- if (data) this._patch(data);
59
+ this._patch(data);
66
60
  }
67
61
 
68
62
  _patch(data) {
@@ -95,7 +89,17 @@ class GuildMember extends Base {
95
89
  this.banner ??= null;
96
90
  }
97
91
 
98
- if ('joined_at' in data) this.joinedTimestamp = Date.parse(data.joined_at);
92
+ if ('joined_at' in data) {
93
+ /**
94
+ * The timestamp the member joined the guild at
95
+ *
96
+ * @type {?number}
97
+ */
98
+ this.joinedTimestamp = data.joined_at && Date.parse(data.joined_at);
99
+ } else {
100
+ this.joinedTimestamp ??= null;
101
+ }
102
+
99
103
  if ('premium_since' in data) {
100
104
  this.premiumSinceTimestamp = data.premium_since ? Date.parse(data.premium_since) : null;
101
105
  }
@@ -22,7 +22,7 @@ class PermissionOverwrites extends Base {
22
22
  */
23
23
  Object.defineProperty(this, 'channel', { value: channel });
24
24
 
25
- if (data) this._patch(data);
25
+ this._patch(data);
26
26
  }
27
27
 
28
28
  _patch(data) {
@@ -34,7 +34,7 @@ class Role extends Base {
34
34
  */
35
35
  this.unicodeEmoji = null;
36
36
 
37
- if (data) this._patch(data);
37
+ this._patch(data);
38
38
  }
39
39
 
40
40
  _patch(data) {
@@ -49,7 +49,7 @@ class ThreadChannel extends BaseChannel {
49
49
  * @type {ThreadMemberManager}
50
50
  */
51
51
  this.members = new ThreadMemberManager(this);
52
- if (data) this._patch(data);
52
+ this._patch(data);
53
53
  }
54
54
 
55
55
  _patch(data) {
@@ -22,7 +22,7 @@ class Webhook {
22
22
  * @readonly
23
23
  */
24
24
  Object.defineProperty(this, 'client', { value: client });
25
- if (data) this._patch(data);
25
+ this._patch(data);
26
26
  }
27
27
 
28
28
  _patch(data) {
@@ -3841,7 +3841,7 @@ export interface PrivateThreadChannel extends ThreadChannel<false> {
3841
3841
  export interface ThreadChannel<ThreadOnly extends boolean = boolean>
3842
3842
  extends Omit<TextBasedChannelFields<true>, 'fetchWebhooks' | 'createWebhook' | 'setNSFW'> {}
3843
3843
  export class ThreadChannel<ThreadOnly extends boolean = boolean> extends BaseChannel {
3844
- private constructor(guild: Guild, data?: RawThreadChannelData, client?: Client<true>);
3844
+ private constructor(guild: Guild, data: RawThreadChannelData, client?: Client<true>);
3845
3845
  public archived: boolean | null;
3846
3846
  public get archivedAt(): Date | null;
3847
3847
  public archiveTimestamp: number | null;
@@ -4234,7 +4234,7 @@ export class VoiceState extends Base {
4234
4234
  // tslint:disable-next-line no-empty-interface
4235
4235
  export interface Webhook<Type extends WebhookType = WebhookType> extends WebhookFields {}
4236
4236
  export class Webhook<Type extends WebhookType = WebhookType> {
4237
- private constructor(client: Client<true>, data?: RawWebhookData);
4237
+ private constructor(client: Client<true>, data: RawWebhookData);
4238
4238
  public avatar: string | null;
4239
4239
  public avatarURL(options?: ImageURLOptions): string | null;
4240
4240
  public channelId: Snowflake;
@@ -3841,7 +3841,7 @@ export interface PrivateThreadChannel extends ThreadChannel<false> {
3841
3841
  export interface ThreadChannel<ThreadOnly extends boolean = boolean>
3842
3842
  extends Omit<TextBasedChannelFields<true>, 'fetchWebhooks' | 'createWebhook' | 'setNSFW'> {}
3843
3843
  export class ThreadChannel<ThreadOnly extends boolean = boolean> extends BaseChannel {
3844
- private constructor(guild: Guild, data?: RawThreadChannelData, client?: Client<true>);
3844
+ private constructor(guild: Guild, data: RawThreadChannelData, client?: Client<true>);
3845
3845
  public archived: boolean | null;
3846
3846
  public get archivedAt(): Date | null;
3847
3847
  public archiveTimestamp: number | null;
@@ -4234,7 +4234,7 @@ export class VoiceState extends Base {
4234
4234
  // tslint:disable-next-line no-empty-interface
4235
4235
  export interface Webhook<Type extends WebhookType = WebhookType> extends WebhookFields {}
4236
4236
  export class Webhook<Type extends WebhookType = WebhookType> {
4237
- private constructor(client: Client<true>, data?: RawWebhookData);
4237
+ private constructor(client: Client<true>, data: RawWebhookData);
4238
4238
  public avatar: string | null;
4239
4239
  public avatarURL(options?: ImageURLOptions): string | null;
4240
4240
  public channelId: Snowflake;