discord.js 14.25.0 → 14.26.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.25.0",
4
+ "version": "14.26.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",
@@ -56,32 +56,32 @@
56
56
  "@discordjs/collection": "1.5.3",
57
57
  "@discordjs/ws": "^1.2.3",
58
58
  "@sapphire/snowflake": "3.5.3",
59
- "discord-api-types": "^0.38.33",
59
+ "discord-api-types": "^0.38.40",
60
60
  "fast-deep-equal": "3.1.3",
61
61
  "lodash.snakecase": "4.1.1",
62
- "magic-bytes.js": "^1.10.0",
62
+ "magic-bytes.js": "^1.13.0",
63
63
  "tslib": "^2.6.3",
64
- "undici": "6.21.3",
65
- "@discordjs/builders": "^1.13.0",
66
- "@discordjs/formatters": "^0.6.2",
67
- "@discordjs/rest": "^2.6.0",
68
- "@discordjs/util": "^1.2.0"
64
+ "undici": "6.24.1",
65
+ "@discordjs/builders": "^1.14.0",
66
+ "@discordjs/rest": "^2.6.1",
67
+ "@discordjs/util": "^1.2.0",
68
+ "@discordjs/formatters": "^0.6.2"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@favware/cliff-jumper": "^4.1.0",
72
- "@types/node": "^16.18.105",
73
- "@typescript-eslint/eslint-plugin": "^8.2.0",
74
- "@typescript-eslint/parser": "^8.2.0",
72
+ "@types/node": "^16.18.126",
73
+ "@typescript-eslint/eslint-plugin": "^8.56.0",
74
+ "@typescript-eslint/parser": "^8.56.0",
75
75
  "cross-env": "^7.0.3",
76
76
  "dtslint": "4.2.1",
77
- "eslint": "^8.57.0",
77
+ "eslint": "^8.57.1",
78
78
  "eslint-formatter-pretty": "^5.0.0",
79
79
  "jest": "29.7.0",
80
- "prettier": "^3.3.3",
80
+ "prettier": "^3.8.1",
81
81
  "tsd": "^0.31.1",
82
82
  "tslint": "6.1.3",
83
- "turbo": "^2.0.14",
84
- "typescript": "~5.5.4",
83
+ "turbo": "^2.8.10",
84
+ "typescript": "~5.8.3",
85
85
  "@discordjs/api-extractor": "^7.38.1",
86
86
  "@discordjs/docgen": "^0.12.1",
87
87
  "@discordjs/scripts": "^0.1.0"
@@ -33,10 +33,16 @@ class GenericAction {
33
33
  const payloadData = {};
34
34
  const id = data.channel_id ?? data.id;
35
35
 
36
- if (!('recipients' in data)) {
37
- // Try to resolve the recipient, but do not add the client user.
36
+ if ('recipients' in data) {
37
+ // Try to resolve the recipient, but do not add if already existing in recipients.
38
38
  const recipient = data.author ?? data.user ?? { id: data.user_id };
39
- if (recipient.id !== this.client.user.id) payloadData.recipients = [recipient];
39
+ if (!data.recipients.some(existingRecipient => recipient.id === existingRecipient.id)) {
40
+ payloadData.recipients = [...data.recipients, recipient];
41
+ }
42
+ } else {
43
+ // Try to resolve the recipient.
44
+ const recipient = data.author ?? data.user ?? { id: data.user_id };
45
+ payloadData.recipients = [recipient];
40
46
  }
41
47
 
42
48
  if (id !== undefined) payloadData.id = id;
@@ -21,7 +21,9 @@ class InteractionCreateAction extends Action {
21
21
  const client = this.client;
22
22
 
23
23
  // Resolve and cache partial channels for Interaction#channel getter
24
- const channel = data.channel && this.getChannel(data.channel);
24
+ const channel =
25
+ data.channel &&
26
+ this.getChannel({ ...data.channel, ...('recipients' in data.channel ? { user: data.user } : undefined) });
25
27
 
26
28
  // Do not emit this for interactions that cache messages that are non-text-based.
27
29
  let InteractionClass;
@@ -11,15 +11,19 @@ const Messages = require('./Messages');
11
11
  * @ignore
12
12
  */
13
13
  function makeDiscordjsError(Base) {
14
- return class DiscordjsError extends Base {
14
+ return class extends Base {
15
+ static {
16
+ Object.defineProperty(this, 'name', { value: `Discordjs${Base.name}` });
17
+ }
18
+
15
19
  constructor(code, ...args) {
16
20
  super(message(code, args));
17
21
  this.code = code;
18
- Error.captureStackTrace?.(this, DiscordjsError);
22
+ Error.captureStackTrace(this, this.constructor);
19
23
  }
20
24
 
21
25
  get name() {
22
- return `${super.name} [${this.code}]`;
26
+ return `${this.constructor.name} [${this.code}]`;
23
27
  }
24
28
  };
25
29
  }
@@ -115,7 +115,8 @@ const Messages = {
115
115
  [DjsErrorCodes.EmojiType]: 'Emoji must be a string or GuildEmoji/ReactionEmoji',
116
116
  [DjsErrorCodes.EmojiManaged]: 'Emoji is managed and has no Author.',
117
117
  [DjsErrorCodes.MissingManageGuildExpressionsPermission]: guild =>
118
- `Client must have Manage Guild Expressions permission in guild ${guild} to see emoji authors.`,
118
+ // eslint-disable-next-line max-len
119
+ `Client must have Create Guild Expressions or Manage Guild Expressions permission in guild ${guild} to see emoji authors.`,
119
120
  [DjsErrorCodes.MissingManageEmojisAndStickersPermission]: guild =>
120
121
  `Client must have Manage Emojis and Stickers permission in guild ${guild} to see emoji authors.`,
121
122
 
@@ -161,7 +161,7 @@ class GuildEmojiManager extends BaseGuildEmojiManager {
161
161
 
162
162
  const { me } = this.guild.members;
163
163
  if (!me) throw new DiscordjsError(ErrorCodes.GuildUncachedMe);
164
- if (!me.permissions.has(PermissionFlagsBits.ManageGuildExpressions)) {
164
+ if (!me.permissions.any(PermissionFlagsBits.CreateGuildExpressions | PermissionFlagsBits.ManageGuildExpressions)) {
165
165
  throw new DiscordjsError(ErrorCodes.MissingManageGuildExpressionsPermission, this.guild);
166
166
  }
167
167
 
@@ -41,9 +41,12 @@ class UserManager extends CachedManager {
41
41
  * @private
42
42
  */
43
43
  dmChannel(userId) {
44
+ const expectedRecipientIds = [userId, this.client.user.id];
44
45
  return (
45
- this.client.channels.cache.find(channel => channel.type === ChannelType.DM && channel.recipientId === userId) ??
46
- null
46
+ this.client.channels.cache.find(
47
+ channel =>
48
+ channel.type === ChannelType.DM && channel.recipientIds.every(id => expectedRecipientIds.includes(id)),
49
+ ) ?? null
47
50
  );
48
51
  }
49
52
 
@@ -30,16 +30,18 @@ class DMChannel extends BaseChannel {
30
30
  super._patch(data);
31
31
 
32
32
  if (data.recipients) {
33
- const recipient = data.recipients[0];
34
-
35
33
  /**
36
- * The recipient's id
37
- * @type {Snowflake}
34
+ * The recipients' ids
35
+ * @type {Snowflake[]}
38
36
  */
39
- this.recipientId = recipient.id;
40
-
41
- if ('username' in recipient || this.client.options.partials.includes(Partials.User)) {
42
- this.client.users._add(recipient);
37
+ this.recipientIds = [
38
+ ...new Set([...(this.recipientIds ?? []), ...data.recipients.map(recipient => recipient.id)]),
39
+ ];
40
+
41
+ for (const recipient of data.recipients) {
42
+ if ('username' in recipient || this.client.options.partials.includes(Partials.User)) {
43
+ this.client.users._add(recipient);
44
+ }
43
45
  }
44
46
  }
45
47
 
@@ -71,6 +73,18 @@ class DMChannel extends BaseChannel {
71
73
  return this.lastMessageId === undefined;
72
74
  }
73
75
 
76
+ /**
77
+ * The recipient's id.
78
+ * <info>For DMChannel the client user is not a part of this might return a wrong id.
79
+ * This will return `null` in the next major version.</info>
80
+ * @type {Snowflake}
81
+ * @readonly
82
+ */
83
+ get recipientId() {
84
+ // To not be a breaking change this returns the arbitrary first id if this is not a DMChannel with the client user
85
+ return this.recipientIds.find(recipientId => recipientId !== this.client.user.id) ?? this.recipientIds[0];
86
+ }
87
+
74
88
  /**
75
89
  * The recipient on the other end of the DM
76
90
  * @type {?User}
@@ -256,7 +256,7 @@ class Invite extends Base {
256
256
  if (!guild.members.me) throw new DiscordjsError(ErrorCodes.GuildUncachedMe);
257
257
  return Boolean(
258
258
  this.channel?.permissionsFor(this.client.user).has(PermissionFlagsBits.ManageChannels, false) ||
259
- guild.members.me.permissions.has(PermissionFlagsBits.ManageGuild),
259
+ guild.members.me.permissions.has(PermissionFlagsBits.ManageGuild),
260
260
  );
261
261
  }
262
262
 
@@ -711,8 +711,8 @@ class Message extends Base {
711
711
  get editable() {
712
712
  const precheck = Boolean(
713
713
  this.author.id === this.client.user.id &&
714
- (!this.guild || this.channel?.viewable) &&
715
- this.reference?.type !== MessageReferenceType.Forward,
714
+ (!this.guild || this.channel?.viewable) &&
715
+ this.reference?.type !== MessageReferenceType.Forward,
716
716
  );
717
717
 
718
718
  // Regardless of permissions thread messages cannot be edited if
@@ -788,10 +788,7 @@ class Message extends Base {
788
788
  const permissions = channel?.permissionsFor(this.client.user);
789
789
  if (!permissions) return false;
790
790
 
791
- return (
792
- permissions.has(PermissionFlagsBits.ReadMessageHistory | PermissionFlagsBits.PinMessages) ||
793
- permissions.has(PermissionFlagsBits.ReadMessageHistory | PermissionFlagsBits.ManageMessages)
794
- );
791
+ return permissions.has(PermissionFlagsBits.ReadMessageHistory | PermissionFlagsBits.PinMessages);
795
792
  }
796
793
 
797
794
  /**
@@ -820,12 +817,12 @@ class Message extends Base {
820
817
  const { channel } = this;
821
818
  return Boolean(
822
819
  channel?.type === ChannelType.GuildAnnouncement &&
823
- !this.flags.has(MessageFlags.Crossposted) &&
824
- this.reference?.type !== MessageReferenceType.Forward &&
825
- this.type === MessageType.Default &&
826
- !this.poll &&
827
- channel.viewable &&
828
- channel.permissionsFor(this.client.user)?.has(bitfield, false),
820
+ !this.flags.has(MessageFlags.Crossposted) &&
821
+ this.reference?.type !== MessageReferenceType.Forward &&
822
+ this.type === MessageType.Default &&
823
+ !this.poll &&
824
+ channel.viewable &&
825
+ channel.permissionsFor(this.client.user)?.has(bitfield, false),
829
826
  );
830
827
  }
831
828
 
@@ -227,6 +227,37 @@ class ModalSubmitFields {
227
227
  getUploadedFiles(customId, required = false) {
228
228
  return this._getTypedComponent(customId, [ComponentType.FileUpload], ['attachments'], required).attachments ?? null;
229
229
  }
230
+
231
+ /**
232
+ * Get radio group component
233
+ *
234
+ * @param {string} customId The custom id of the component
235
+ * @param {boolean} [required=false] Whether to throw an error if the component value is not found or empty
236
+ * @returns {?string} The selected radio group option value, or null if none were selected and not required
237
+ */
238
+ getRadioGroup(customId, required = false) {
239
+ return this._getTypedComponent(customId, [ComponentType.RadioGroup], ['value'], required).value;
240
+ }
241
+
242
+ /**
243
+ * Get checkbox group component
244
+ *
245
+ * @param {string} customId The custom id of the component
246
+ * @returns {string[]} The selected checkbox group option values
247
+ */
248
+ getCheckboxGroup(customId) {
249
+ return this._getTypedComponent(customId, [ComponentType.CheckboxGroup]).values;
250
+ }
251
+
252
+ /**
253
+ * Get checkbox component
254
+ *
255
+ * @param {string} customId The custom id of the component
256
+ * @returns {boolean} Whether this checkbox was selected
257
+ */
258
+ getCheckbox(customId) {
259
+ return this._getTypedComponent(customId, [ComponentType.Checkbox]).value;
260
+ }
230
261
  }
231
262
 
232
263
  module.exports = ModalSubmitFields;
@@ -24,6 +24,24 @@ const getAttachment = lazy(() => require('./Attachment'));
24
24
  * @property {Collection<Snowflake, Attachment>} [attachments] The resolved attachments
25
25
  */
26
26
 
27
+ /**
28
+ * @typedef {BaseModalData} RadioGroupModalData
29
+ * @property {string} customId The custom id of the radio group
30
+ * @property {?string} value The value selected for the radio group
31
+ */
32
+
33
+ /**
34
+ * @typedef {BaseModalData} CheckboxGroupModalData
35
+ * @property {string} customId The custom id of the checkbox group
36
+ * @property {string[]} values The values selected for the checkbox group
37
+ */
38
+
39
+ /**
40
+ * @typedef {BaseModalData} CheckboxModalData
41
+ * @property {string} customId The custom id of the checkbox
42
+ * @property {boolean} value Whether this checkbox was selected
43
+ */
44
+
27
45
  /**
28
46
  * @typedef {BaseModalData} TextInputModalData
29
47
  * @property {string} customId The custom id of the field
@@ -45,7 +63,8 @@ const getAttachment = lazy(() => require('./Attachment'));
45
63
  */
46
64
 
47
65
  /**
48
- * @typedef {SelectMenuModalData|TextInputModalData|FileUploadModalData} ModalData
66
+ * @typedef {SelectMenuModalData|TextInputModalData|FileUploadModalData|RadioGroupModalData|
67
+ * CheckboxGroupModalData|CheckboxModalData} ModalData
49
68
  */
50
69
 
51
70
  /**
@@ -24,7 +24,8 @@ const { ComponentType } = require('discord-api-types/v10');
24
24
  /**
25
25
  * @typedef {StringSelectMenuComponentData|TextInputComponentData|UserSelectMenuComponentData|
26
26
  * RoleSelectMenuComponentData|MentionableSelectMenuComponentData|ChannelSelectMenuComponentData|
27
- * FileUploadComponentData} ComponentInLabelData
27
+ * FileUploadComponentData|RadioGroupComponentData|CheckboxGroupComponentData|
28
+ * CheckboxComponentData} ComponentInLabelData
28
29
  */
29
30
 
30
31
  /**
@@ -52,6 +53,44 @@ const { ComponentType } = require('discord-api-types/v10');
52
53
  * @property {boolean} [required] Whether this component is required in modals
53
54
  */
54
55
 
56
+ /**
57
+ * @typedef {Object} RadioGroupOption
58
+ * @property {string} value The value of the radio group option
59
+ * @property {string} label The label to use
60
+ * @property {string} [description] The optional description for the radio group option
61
+ * @property {boolean} [default] Whether this option is default selected
62
+ */
63
+
64
+ /**
65
+ * @typedef {BaseComponentData} RadioGroupComponentData
66
+ * @property {string} customId The custom id of the radio group
67
+ * @property {RadioGroupOption[]} options The options in this radio group (2-10)
68
+ * @property {boolean} [required] Whether this component is required in modals
69
+ */
70
+
71
+ /**
72
+ * @typedef {Object} CheckboxGroupOption
73
+ * @property {string} value The value of the checkbox group option
74
+ * @property {string} label The label to use
75
+ * @property {string} [description] The optional description for the checkbox group option
76
+ * @property {boolean} [default] Whether this option is default selected
77
+ */
78
+
79
+ /**
80
+ * @typedef {BaseComponentData} CheckboxGroupComponentData
81
+ * @property {string} customId The custom id of the checkbox group
82
+ * @property {CheckboxGroupOption[]} options The options in this checkbox group
83
+ * @property {number} [minValues] The minimum number of options that must be selected (0-10)
84
+ * @property {number} [maxValues] The maximum number of options that can be selected (defaults to options length)
85
+ * @property {boolean} [required] Whether this component is required in modals
86
+ */
87
+
88
+ /**
89
+ * @typedef {BaseComponentData} CheckboxComponentData
90
+ * @property {string} customId The custom id of the checkbox
91
+ * @property {boolean} [default] Whether this component is default selected in modals
92
+ */
93
+
55
94
  /**
56
95
  * @typedef {BaseComponentData} BaseSelectMenuComponentData
57
96
  * @property {string} customId The custom id of the select menu