discord.js 15.0.0-dev.1745453588-abc5d99ce → 15.0.0-dev.1745626383-8f375275c

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.
Files changed (38) hide show
  1. package/package.json +4 -3
  2. package/src/client/Client.js +14 -0
  3. package/src/client/actions/Action.js +4 -0
  4. package/src/client/actions/ActionsManager.js +1 -0
  5. package/src/client/actions/GuildSoundboardSoundDelete.js +29 -0
  6. package/src/client/actions/InteractionCreate.js +4 -0
  7. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUNDS_UPDATE.js +24 -0
  8. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUND_CREATE.js +18 -0
  9. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUND_DELETE.js +5 -0
  10. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUND_UPDATE.js +20 -0
  11. package/src/client/websocket/handlers/SOUNDBOARD_SOUNDS.js +24 -0
  12. package/src/client/websocket/handlers/index.js +5 -0
  13. package/src/errors/ErrorCodes.js +5 -0
  14. package/src/errors/Messages.js +3 -0
  15. package/src/index.js +4 -0
  16. package/src/managers/ApplicationCommandManager.js +1 -0
  17. package/src/managers/GuildManager.js +67 -1
  18. package/src/managers/GuildSoundboardSoundManager.js +192 -0
  19. package/src/structures/ApplicationCommand.js +21 -3
  20. package/src/structures/BaseInteraction.js +10 -0
  21. package/src/structures/CommandInteraction.js +1 -0
  22. package/src/structures/Guild.js +7 -0
  23. package/src/structures/GuildAuditLogsEntry.js +4 -5
  24. package/src/structures/MessageComponentInteraction.js +1 -0
  25. package/src/structures/ModalSubmitInteraction.js +1 -0
  26. package/src/structures/PrimaryEntryPointCommandInteraction.js +11 -0
  27. package/src/structures/SoundboardSound.js +204 -0
  28. package/src/structures/Sticker.js +1 -1
  29. package/src/structures/VoiceChannel.js +21 -1
  30. package/src/structures/VoiceChannelEffect.js +9 -0
  31. package/src/structures/interfaces/InteractionResponses.js +26 -0
  32. package/src/util/APITypes.js +5 -0
  33. package/src/util/DataResolver.js +5 -4
  34. package/src/util/Events.js +10 -0
  35. package/src/util/Partials.js +2 -0
  36. package/typings/index.d.mts +152 -19
  37. package/typings/index.d.ts +152 -19
  38. package/typings/index.test-d.ts +112 -0
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": "15.0.0-dev.1745453588-abc5d99ce",
4
+ "version": "15.0.0-dev.1745626383-8f375275c",
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",
@@ -57,12 +57,13 @@
57
57
  "discord-api-types": "^0.38.1",
58
58
  "fast-deep-equal": "3.1.3",
59
59
  "lodash.snakecase": "4.1.1",
60
+ "magic-bytes.js": "^1.10.0",
60
61
  "tslib": "^2.8.1",
61
62
  "undici": "7.8.0",
62
- "@discordjs/collection": "^2.1.1",
63
63
  "@discordjs/builders": "^1.9.0",
64
- "@discordjs/rest": "^2.4.0",
65
64
  "@discordjs/formatters": "^0.5.0",
65
+ "@discordjs/rest": "^2.4.0",
66
+ "@discordjs/collection": "^2.1.1",
66
67
  "@discordjs/util": "^1.1.1",
67
68
  "@discordjs/ws": "^2.0.0"
68
69
  },
@@ -19,6 +19,7 @@ const { ClientPresence } = require('../structures/ClientPresence.js');
19
19
  const { GuildPreview } = require('../structures/GuildPreview.js');
20
20
  const { GuildTemplate } = require('../structures/GuildTemplate.js');
21
21
  const { Invite } = require('../structures/Invite.js');
22
+ const { SoundboardSound } = require('../structures/SoundboardSound.js');
22
23
  const { Sticker } = require('../structures/Sticker.js');
23
24
  const { StickerPack } = require('../structures/StickerPack.js');
24
25
  const { VoiceRegion } = require('../structures/VoiceRegion.js');
@@ -536,6 +537,19 @@ class Client extends BaseClient {
536
537
  return new Collection(data.sticker_packs.map(stickerPack => [stickerPack.id, new StickerPack(this, stickerPack)]));
537
538
  }
538
539
 
540
+ /**
541
+ * Obtains the list of default soundboard sounds.
542
+ * @returns {Promise<Collection<string, SoundboardSound>>}
543
+ * @example
544
+ * client.fetchDefaultSoundboardSounds()
545
+ * .then(sounds => console.log(`Available soundboard sounds are: ${sounds.map(sound => sound.name).join(', ')}`))
546
+ * .catch(console.error);
547
+ */
548
+ async fetchDefaultSoundboardSounds() {
549
+ const data = await this.rest.get(Routes.soundboardDefaultSounds());
550
+ return new Collection(data.map(sound => [sound.sound_id, new SoundboardSound(this, sound)]));
551
+ }
552
+
539
553
  /**
540
554
  * Obtains a guild preview from Discord, available for all guilds the bot is in and all Discoverable guilds.
541
555
  * @param {GuildResolvable} guild The guild to fetch the preview for
@@ -131,6 +131,10 @@ class Action {
131
131
  return this.getPayload({ user_id: id }, manager, id, Partials.ThreadMember, false);
132
132
  }
133
133
 
134
+ getSoundboardSound(data, guild) {
135
+ return this.getPayload(data, guild.soundboardSounds, data.sound_id, Partials.SoundboardSound);
136
+ }
137
+
134
138
  spreadInjectedData(data) {
135
139
  return Object.fromEntries(Object.getOwnPropertySymbols(data).map(symbol => [symbol, data[symbol]]));
136
140
  }
@@ -27,6 +27,7 @@ class ActionsManager {
27
27
  this.register(require('./GuildScheduledEventDelete.js').GuildScheduledEventDeleteAction);
28
28
  this.register(require('./GuildScheduledEventUserAdd.js').GuildScheduledEventUserAddAction);
29
29
  this.register(require('./GuildScheduledEventUserRemove.js').GuildScheduledEventUserRemoveAction);
30
+ this.register(require('./GuildSoundboardSoundDelete.js').GuildSoundboardSoundDeleteAction);
30
31
  this.register(require('./GuildStickerCreate.js').GuildStickerCreateAction);
31
32
  this.register(require('./GuildStickerDelete.js').GuildStickerDeleteAction);
32
33
  this.register(require('./GuildStickerUpdate.js').GuildStickerUpdateAction);
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const { Action } = require('./Action.js');
4
+ const { Events } = require('../../util/Events.js');
5
+
6
+ class GuildSoundboardSoundDeleteAction extends Action {
7
+ handle(data) {
8
+ const guild = this.client.guilds.cache.get(data.guild_id);
9
+
10
+ if (!guild) return {};
11
+
12
+ const soundboardSound = this.getSoundboardSound(data, guild);
13
+
14
+ if (soundboardSound) {
15
+ guild.soundboardSounds.cache.delete(soundboardSound.soundId);
16
+
17
+ /**
18
+ * Emitted whenever a soundboard sound is deleted in a guild.
19
+ * @event Client#guildSoundboardSoundDelete
20
+ * @param {SoundboardSound} soundboardSound The soundboard sound that was deleted
21
+ */
22
+ this.client.emit(Events.GuildSoundboardSoundDelete, soundboardSound);
23
+ }
24
+
25
+ return { soundboardSound };
26
+ }
27
+ }
28
+
29
+ exports.GuildSoundboardSoundDeleteAction = GuildSoundboardSoundDeleteAction;
@@ -9,6 +9,7 @@ const { ChatInputCommandInteraction } = require('../../structures/ChatInputComma
9
9
  const { MentionableSelectMenuInteraction } = require('../../structures/MentionableSelectMenuInteraction.js');
10
10
  const { MessageContextMenuCommandInteraction } = require('../../structures/MessageContextMenuCommandInteraction.js');
11
11
  const { ModalSubmitInteraction } = require('../../structures/ModalSubmitInteraction.js');
12
+ const { PrimaryEntryPointCommandInteraction } = require('../../structures/PrimaryEntryPointCommandInteraction.js');
12
13
  const { RoleSelectMenuInteraction } = require('../../structures/RoleSelectMenuInteraction.js');
13
14
  const { StringSelectMenuInteraction } = require('../../structures/StringSelectMenuInteraction.js');
14
15
  const { UserContextMenuCommandInteraction } = require('../../structures/UserContextMenuCommandInteraction.js');
@@ -38,6 +39,9 @@ class InteractionCreateAction extends Action {
38
39
  if (channel && !channel.isTextBased()) return;
39
40
  InteractionClass = MessageContextMenuCommandInteraction;
40
41
  break;
42
+ case ApplicationCommandType.PrimaryEntryPoint:
43
+ InteractionClass = PrimaryEntryPointCommandInteraction;
44
+ break;
41
45
  default:
42
46
  client.emit(
43
47
  Events.Debug,
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const { Collection } = require('@discordjs/collection');
4
+ const { Events } = require('../../../util/Events.js');
5
+
6
+ module.exports = (client, { d: data }) => {
7
+ const guild = client.guilds.cache.get(data.guild_id);
8
+
9
+ if (!guild) return;
10
+
11
+ const soundboardSounds = new Collection();
12
+
13
+ for (const soundboardSound of data.soundboard_sounds) {
14
+ soundboardSounds.set(soundboardSound.sound_id, guild.soundboardSounds._add(soundboardSound));
15
+ }
16
+
17
+ /**
18
+ * Emitted whenever multiple guild soundboard sounds are updated.
19
+ * @event Client#guildSoundboardSoundsUpdate
20
+ * @param {Collection<Snowflake, SoundboardSound>} soundboardSounds The updated soundboard sounds
21
+ * @param {Guild} guild The guild that the soundboard sounds are from
22
+ */
23
+ client.emit(Events.GuildSoundboardSoundsUpdate, soundboardSounds, guild);
24
+ };
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const { Events } = require('../../../util/Events.js');
4
+
5
+ module.exports = (client, { d: data }) => {
6
+ const guild = client.guilds.cache.get(data.guild_id);
7
+
8
+ if (!guild) return;
9
+
10
+ const soundboardSound = guild.soundboardSounds._add(data);
11
+
12
+ /**
13
+ * Emitted whenever a guild soundboard sound is created.
14
+ * @event Client#guildSoundboardSoundCreate
15
+ * @param {SoundboardSound} soundboardSound The created guild soundboard sound
16
+ */
17
+ client.emit(Events.GuildSoundboardSoundCreate, soundboardSound);
18
+ };
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ module.exports = (client, { d: data }) => {
4
+ client.actions.GuildSoundboardSoundDelete.handle(data);
5
+ };
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const { Events } = require('../../../util/Events.js');
4
+
5
+ module.exports = (client, { d: data }) => {
6
+ const guild = client.guilds.cache.get(data.guild_id);
7
+
8
+ if (!guild) return;
9
+
10
+ const oldGuildSoundboardSound = guild.soundboardSounds.cache.get(data.sound_id)?._clone() ?? null;
11
+ const newGuildSoundboardSound = guild.soundboardSounds._add(data);
12
+
13
+ /**
14
+ * Emitted whenever a guild soundboard sound is updated.
15
+ * @event Client#guildSoundboardSoundUpdate
16
+ * @param {?SoundboardSound} oldGuildSoundboardSound The guild soundboard sound before the update
17
+ * @param {SoundboardSound} newGuildSoundboardSound The guild soundboard sound after the update
18
+ */
19
+ client.emit(Events.GuildSoundboardSoundUpdate, oldGuildSoundboardSound, newGuildSoundboardSound);
20
+ };
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const { Collection } = require('@discordjs/collection');
4
+ const { Events } = require('../../../util/Events.js');
5
+
6
+ module.exports = (client, { d: data }) => {
7
+ const guild = client.guilds.cache.get(data.guild_id);
8
+
9
+ if (!guild) return;
10
+
11
+ const soundboardSounds = new Collection();
12
+
13
+ for (const soundboardSound of data.soundboard_sounds) {
14
+ soundboardSounds.set(soundboardSound.sound_id, guild.soundboardSounds._add(soundboardSound));
15
+ }
16
+
17
+ /**
18
+ * Emitted whenever soundboard sounds are received (all soundboard sounds come from the same guild).
19
+ * @event Client#soundboardSounds
20
+ * @param {Collection<Snowflake, SoundboardSound>} soundboardSounds The sounds received
21
+ * @param {Guild} guild The guild that the soundboard sounds are from
22
+ */
23
+ client.emit(Events.SoundboardSounds, soundboardSounds, guild);
24
+ };
@@ -32,6 +32,10 @@ const PacketHandlers = Object.fromEntries([
32
32
  ['GUILD_SCHEDULED_EVENT_UPDATE', require('./GUILD_SCHEDULED_EVENT_UPDATE.js')],
33
33
  ['GUILD_SCHEDULED_EVENT_USER_ADD', require('./GUILD_SCHEDULED_EVENT_USER_ADD.js')],
34
34
  ['GUILD_SCHEDULED_EVENT_USER_REMOVE', require('./GUILD_SCHEDULED_EVENT_USER_REMOVE.js')],
35
+ ['GUILD_SOUNDBOARD_SOUNDS_UPDATE', require('./GUILD_SOUNDBOARD_SOUNDS_UPDATE.js')],
36
+ ['GUILD_SOUNDBOARD_SOUND_CREATE', require('./GUILD_SOUNDBOARD_SOUND_CREATE.js')],
37
+ ['GUILD_SOUNDBOARD_SOUND_DELETE', require('./GUILD_SOUNDBOARD_SOUND_DELETE.js')],
38
+ ['GUILD_SOUNDBOARD_SOUND_UPDATE', require('./GUILD_SOUNDBOARD_SOUND_UPDATE.js')],
35
39
  ['GUILD_STICKERS_UPDATE', require('./GUILD_STICKERS_UPDATE.js')],
36
40
  ['GUILD_UPDATE', require('./GUILD_UPDATE.js')],
37
41
  ['INTERACTION_CREATE', require('./INTERACTION_CREATE.js')],
@@ -49,6 +53,7 @@ const PacketHandlers = Object.fromEntries([
49
53
  ['MESSAGE_UPDATE', require('./MESSAGE_UPDATE.js')],
50
54
  ['PRESENCE_UPDATE', require('./PRESENCE_UPDATE.js')],
51
55
  ['READY', require('./READY.js')],
56
+ ['SOUNDBOARD_SOUNDS', require('./SOUNDBOARD_SOUNDS.js')],
52
57
  ['STAGE_INSTANCE_CREATE', require('./STAGE_INSTANCE_CREATE.js')],
53
58
  ['STAGE_INSTANCE_DELETE', require('./STAGE_INSTANCE_DELETE.js')],
54
59
  ['STAGE_INSTANCE_UPDATE', require('./STAGE_INSTANCE_UPDATE.js')],
@@ -61,6 +61,7 @@
61
61
  * @property {'GuildChannelUnowned'} GuildChannelUnowned
62
62
  * @property {'GuildOwned'} GuildOwned
63
63
  * @property {'GuildMembersTimeout'} GuildMembersTimeout
64
+ * @property {'GuildSoundboardSoundsTimeout'} GuildSoundboardSoundsTimeout
64
65
  * @property {'GuildUncachedMe'} GuildUncachedMe
65
66
  * @property {'ChannelNotCached'} ChannelNotCached
66
67
  * @property {'StageChannelResolve'} StageChannelResolve
@@ -85,6 +86,8 @@
85
86
  * @property {'EmojiManaged'} EmojiManaged
86
87
  * @property {'MissingManageGuildExpressionsPermission'} MissingManageGuildExpressionsPermission
87
88
  *
89
+
90
+ * @property {'NotGuildSoundboardSound'} NotGuildSoundboardSound
88
91
  * @property {'NotGuildSticker'} NotGuildSticker
89
92
 
90
93
  * @property {'ReactionResolveUser'} ReactionResolveUser
@@ -193,6 +196,7 @@ const keys = [
193
196
  'GuildChannelUnowned',
194
197
  'GuildOwned',
195
198
  'GuildMembersTimeout',
199
+ 'GuildSoundboardSoundsTimeout',
196
200
  'GuildUncachedMe',
197
201
  'ChannelNotCached',
198
202
  'StageChannelResolve',
@@ -217,6 +221,7 @@ const keys = [
217
221
  'EmojiManaged',
218
222
  'MissingManageGuildExpressionsPermission',
219
223
 
224
+ 'NotGuildSoundboardSound',
220
225
  'NotGuildSticker',
221
226
 
222
227
  'ReactionResolveUser',
@@ -66,6 +66,7 @@ const Messages = {
66
66
  [ErrorCodes.GuildChannelUnowned]: "The fetched channel does not belong to this manager's guild.",
67
67
  [ErrorCodes.GuildOwned]: 'Guild is owned by the client.',
68
68
  [ErrorCodes.GuildMembersTimeout]: "Members didn't arrive in time.",
69
+ [ErrorCodes.GuildSoundboardSoundsTimeout]: "Soundboard sounds didn't arrive in time.",
69
70
  [ErrorCodes.GuildUncachedMe]: 'The client user as a member of this guild is uncached.',
70
71
  [ErrorCodes.ChannelNotCached]: 'Could not find the channel where this message came from in the cache!',
71
72
  [ErrorCodes.StageChannelResolve]: 'Could not resolve channel to a stage channel.',
@@ -91,6 +92,8 @@ const Messages = {
91
92
  [ErrorCodes.MissingManageGuildExpressionsPermission]: guild =>
92
93
  `Client must have Manage Guild Expressions permission in guild ${guild} to see emoji authors.`,
93
94
 
95
+ [ErrorCodes.NotGuildSoundboardSound]: action =>
96
+ `Soundboard sound is a default (non-guild) soundboard sound and can't be ${action}.`,
94
97
  [ErrorCodes.NotGuildSticker]: 'Sticker is a standard (non-guild) sticker and has no author.',
95
98
 
96
99
  [ErrorCodes.ReactionResolveUser]: "Couldn't resolve the user id to remove from the reaction.",
package/src/index.js CHANGED
@@ -74,6 +74,7 @@ exports.GuildMemberManager = require('./managers/GuildMemberManager.js').GuildMe
74
74
  exports.GuildMemberRoleManager = require('./managers/GuildMemberRoleManager.js').GuildMemberRoleManager;
75
75
  exports.GuildMessageManager = require('./managers/GuildMessageManager.js').GuildMessageManager;
76
76
  exports.GuildScheduledEventManager = require('./managers/GuildScheduledEventManager.js').GuildScheduledEventManager;
77
+ exports.GuildSoundboardSoundManager = require('./managers/GuildSoundboardSoundManager.js').GuildSoundboardSoundManager;
77
78
  exports.GuildStickerManager = require('./managers/GuildStickerManager.js').GuildStickerManager;
78
79
  exports.GuildTextThreadManager = require('./managers/GuildTextThreadManager.js').GuildTextThreadManager;
79
80
  exports.MessageManager = require('./managers/MessageManager.js').MessageManager;
@@ -186,6 +187,8 @@ exports.PartialGroupDMChannel = require('./structures/PartialGroupDMChannel.js')
186
187
  exports.PermissionOverwrites = require('./structures/PermissionOverwrites.js').PermissionOverwrites;
187
188
  exports.Poll = require('./structures/Poll.js').Poll;
188
189
  exports.PollAnswer = require('./structures/PollAnswer.js').PollAnswer;
190
+ exports.PrimaryEntryPointCommandInteraction =
191
+ require('./structures/PrimaryEntryPointCommandInteraction.js').PrimaryEntryPointCommandInteraction;
189
192
  exports.Presence = require('./structures/Presence.js').Presence;
190
193
  exports.ReactionCollector = require('./structures/ReactionCollector.js').ReactionCollector;
191
194
  exports.ReactionEmoji = require('./structures/ReactionEmoji.js').ReactionEmoji;
@@ -194,6 +197,7 @@ exports.Role = require('./structures/Role.js').Role;
194
197
  exports.RoleSelectMenuComponent = require('./structures/RoleSelectMenuComponent.js').RoleSelectMenuComponent;
195
198
  exports.RoleSelectMenuInteraction = require('./structures/RoleSelectMenuInteraction.js').RoleSelectMenuInteraction;
196
199
  exports.SKU = require('./structures/SKU.js').SKU;
200
+ exports.SoundboardSound = require('./structures/SoundboardSound.js').SoundboardSound;
197
201
  exports.StageChannel = require('./structures/StageChannel.js').StageChannel;
198
202
  exports.StageInstance = require('./structures/StageInstance.js').StageInstance;
199
203
  exports.Sticker = require('./structures/Sticker.js').Sticker;
@@ -282,6 +282,7 @@ class ApplicationCommandManager extends CachedManager {
282
282
  default_member_permissions,
283
283
  integration_types: command.integrationTypes ?? command.integration_types,
284
284
  contexts: command.contexts,
285
+ handler: command.handler,
285
286
  };
286
287
  }
287
288
  }
@@ -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 { Routes, RouteBases } = require('discord-api-types/v10');
7
+ const { GatewayOpcodes, Routes, RouteBases } = require('discord-api-types/v10');
8
8
  const { CachedManager } = require('./CachedManager.js');
9
+ const { DiscordjsError, ErrorCodes } = require('../errors/index.js');
9
10
  const { ShardClientUtil } = require('../sharding/ShardClientUtil.js');
10
11
  const { Guild } = require('../structures/Guild.js');
11
12
  const { GuildChannel } = require('../structures/GuildChannel.js');
@@ -282,6 +283,71 @@ class GuildManager extends CachedManager {
282
283
  return data.reduce((coll, guild) => coll.set(guild.id, new OAuth2Guild(this.client, guild)), new Collection());
283
284
  }
284
285
 
286
+ /**
287
+ * @typedef {Object} FetchSoundboardSoundsOptions
288
+ * @param {Snowflake[]} guildIds The ids of the guilds to fetch soundboard sounds for
289
+ * @param {number} [time=10_000] The timeout for receipt of the soundboard sounds
290
+ */
291
+
292
+ /**
293
+ * Fetches soundboard sounds for the specified guilds.
294
+ * @param {FetchSoundboardSoundsOptions} options The options for fetching soundboard sounds
295
+ * @returns {Promise<Collection<Snowflake, Collection<Snowflake, SoundboardSound>>>}
296
+ * @example
297
+ * // Fetch soundboard sounds for multiple guilds
298
+ * const soundboardSounds = await client.guilds.fetchSoundboardSounds({
299
+ * guildIds: ['123456789012345678', '987654321098765432'],
300
+ * })
301
+ *
302
+ * console.log(soundboardSounds.get('123456789012345678'));
303
+ */
304
+ async fetchSoundboardSounds({ guildIds, time = 10_000 }) {
305
+ const shardCount = await this.client.ws.getShardCount();
306
+ const shardIds = Map.groupBy(guildIds, guildId => ShardClientUtil.shardIdForGuildId(guildId, shardCount));
307
+
308
+ for (const [shardId, shardGuildIds] of shardIds) {
309
+ this.client.ws.send(shardId, {
310
+ op: GatewayOpcodes.RequestSoundboardSounds,
311
+ d: {
312
+ guild_ids: shardGuildIds,
313
+ },
314
+ });
315
+ }
316
+
317
+ return new Promise((resolve, reject) => {
318
+ const remainingGuildIds = new Set(guildIds);
319
+
320
+ const fetchedSoundboardSounds = new Collection();
321
+
322
+ const handler = (soundboardSounds, guild) => {
323
+ timeout.refresh();
324
+
325
+ if (!remainingGuildIds.has(guild.id)) return;
326
+
327
+ fetchedSoundboardSounds.set(guild.id, soundboardSounds);
328
+
329
+ remainingGuildIds.delete(guild.id);
330
+
331
+ if (remainingGuildIds.size === 0) {
332
+ clearTimeout(timeout);
333
+ this.client.removeListener(Events.SoundboardSounds, handler);
334
+ this.client.decrementMaxListeners();
335
+
336
+ resolve(fetchedSoundboardSounds);
337
+ }
338
+ };
339
+
340
+ const timeout = setTimeout(() => {
341
+ this.client.removeListener(Events.SoundboardSounds, handler);
342
+ this.client.decrementMaxListeners();
343
+ reject(new DiscordjsError(ErrorCodes.GuildSoundboardSoundsTimeout));
344
+ }, time).unref();
345
+
346
+ this.client.incrementMaxListeners();
347
+ this.client.on(Events.SoundboardSounds, handler);
348
+ });
349
+ }
350
+
285
351
  /**
286
352
  * Options used to set incident actions. Supplying `null` to any option will disable the action.
287
353
  * @typedef {Object} IncidentActionsEditOptions
@@ -0,0 +1,192 @@
1
+ 'use strict';
2
+
3
+ const { Collection } = require('@discordjs/collection');
4
+ const { lazy } = require('@discordjs/util');
5
+ const { Routes } = require('discord-api-types/v10');
6
+ const { CachedManager } = require('./CachedManager.js');
7
+ const { DiscordjsTypeError, ErrorCodes } = require('../errors/index.js');
8
+ const { SoundboardSound } = require('../structures/SoundboardSound.js');
9
+ const { resolveBase64, resolveFile } = require('../util/DataResolver.js');
10
+
11
+ const fileTypeMime = lazy(() => require('magic-bytes.js').filetypemime);
12
+
13
+ /**
14
+ * Manages API methods for Soundboard Sounds and stores their cache.
15
+ * @extends {CachedManager}
16
+ */
17
+ class GuildSoundboardSoundManager extends CachedManager {
18
+ constructor(guild, iterable) {
19
+ super(guild.client, SoundboardSound, iterable);
20
+
21
+ /**
22
+ * The guild this manager belongs to
23
+ * @type {Guild}
24
+ */
25
+ this.guild = guild;
26
+ }
27
+
28
+ /**
29
+ * The cache of Soundboard Sounds
30
+ * @type {Collection<Snowflake, SoundboardSound>}
31
+ * @name GuildSoundboardSoundManager#cache
32
+ */
33
+
34
+ _add(data, cache) {
35
+ return super._add(data, cache, { extras: [this.guild], id: data.sound_id });
36
+ }
37
+
38
+ /**
39
+ * Data that resolves to give a SoundboardSound object. This can be:
40
+ * * A SoundboardSound object
41
+ * * A Snowflake
42
+ * @typedef {SoundboardSound|Snowflake} SoundboardSoundResolvable
43
+ */
44
+
45
+ /**
46
+ * Resolves a SoundboardSoundResolvable to a SoundboardSound object.
47
+ * @method resolve
48
+ * @memberof GuildSoundboardSoundManager
49
+ * @instance
50
+ * @param {SoundboardSoundResolvable} soundboardSound The SoundboardSound resolvable to identify
51
+ * @returns {?SoundboardSound}
52
+ */
53
+
54
+ /**
55
+ * Resolves a {@link SoundboardSoundResolvable} to a {@link SoundboardSound} id.
56
+ * @param {SoundboardSoundResolvable} soundboardSound The soundboard sound resolvable to resolve
57
+ * @returns {?Snowflake}
58
+ */
59
+ resolveId(soundboardSound) {
60
+ if (soundboardSound instanceof this.holds) return soundboardSound.soundId;
61
+ if (typeof soundboardSound === 'string') return soundboardSound;
62
+ return null;
63
+ }
64
+
65
+ /**
66
+ * Options used to create a soundboard sound in a guild.
67
+ * @typedef {Object} GuildSoundboardSoundCreateOptions
68
+ * @property {BufferResolvable|Stream} file The file for the soundboard sound
69
+ * @property {string} name The name for the soundboard sound
70
+ * @property {string} [contentType] The content type for the soundboard sound file
71
+ * @property {number} [volume] The volume (a double) for the soundboard sound, from 0 (inclusive) to 1. Defaults to 1
72
+ * @property {Snowflake} [emojiId] The emoji id for the soundboard sound
73
+ * @property {string} [emojiName] The emoji name for the soundboard sound
74
+ * @property {string} [reason] The reason for creating the soundboard sound
75
+ */
76
+
77
+ /**
78
+ * Creates a new guild soundboard sound.
79
+ * @param {GuildSoundboardSoundCreateOptions} options Options for creating a guild soundboard sound
80
+ * @returns {Promise<SoundboardSound>} The created soundboard sound
81
+ * @example
82
+ * // Create a new soundboard sound from a file on your computer
83
+ * guild.soundboardSounds.create({ file: './sound.mp3', name: 'sound' })
84
+ * .then(sound => console.log(`Created new soundboard sound with name ${sound.name}!`))
85
+ * .catch(console.error);
86
+ */
87
+ async create({ contentType, emojiId, emojiName, file, name, reason, volume }) {
88
+ const resolvedFile = await resolveFile(file);
89
+
90
+ const resolvedContentType = contentType ?? resolvedFile.contentType ?? fileTypeMime()(resolvedFile.data)[0];
91
+
92
+ const sound = resolveBase64(resolvedFile.data, resolvedContentType);
93
+
94
+ const body = { emoji_id: emojiId, emoji_name: emojiName, name, sound, volume };
95
+
96
+ const soundboardSound = await this.client.rest.post(Routes.guildSoundboardSounds(this.guild.id), {
97
+ body,
98
+ reason,
99
+ });
100
+
101
+ return this._add(soundboardSound);
102
+ }
103
+
104
+ /**
105
+ * Data for editing a soundboard sound.
106
+ * @typedef {Object} GuildSoundboardSoundEditOptions
107
+ * @property {string} [name] The name of the soundboard sound
108
+ * @property {?number} [volume] The volume of the soundboard sound, from 0 to 1
109
+ * @property {?Snowflake} [emojiId] The emoji id of the soundboard sound
110
+ * @property {?string} [emojiName] The emoji name of the soundboard sound
111
+ * @property {string} [reason] The reason for editing the soundboard sound
112
+ */
113
+
114
+ /**
115
+ * Edits a soundboard sound.
116
+ * @param {SoundboardSoundResolvable} soundboardSound The soundboard sound to edit
117
+ * @param {GuildSoundboardSoundEditOptions} [options={}] The new data for the soundboard sound
118
+ * @returns {Promise<SoundboardSound>}
119
+ */
120
+ async edit(soundboardSound, options = {}) {
121
+ const soundId = this.resolveId(soundboardSound);
122
+
123
+ if (!soundId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'soundboardSound', 'SoundboardSoundResolvable');
124
+
125
+ const { emojiId, emojiName, name, reason, volume } = options;
126
+
127
+ const body = { emoji_id: emojiId, emoji_name: emojiName, name, volume };
128
+
129
+ const data = await this.client.rest.patch(Routes.guildSoundboardSound(this.guild.id, soundId), {
130
+ body,
131
+ reason,
132
+ });
133
+
134
+ const existing = this.cache.get(soundId);
135
+
136
+ if (existing) {
137
+ const clone = existing._clone();
138
+
139
+ clone._patch(data);
140
+ return clone;
141
+ }
142
+
143
+ return this._add(data);
144
+ }
145
+
146
+ /**
147
+ * Deletes a soundboard sound.
148
+ * @param {SoundboardSoundResolvable} soundboardSound The soundboard sound to delete
149
+ * @param {string} [reason] Reason for deleting this soundboard sound
150
+ * @returns {Promise<void>}
151
+ */
152
+ async delete(soundboardSound, reason) {
153
+ const soundId = this.resolveId(soundboardSound);
154
+
155
+ if (!soundId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'soundboardSound', 'SoundboardSoundResolvable');
156
+
157
+ await this.client.rest.delete(Routes.guildSoundboardSound(this.guild.id, soundId), { reason });
158
+ }
159
+
160
+ /**
161
+ * Obtains one or more soundboard sounds from Discord, or the soundboard sound cache if they're already available.
162
+ * @param {Snowflake} [id] The soundboard sound's id
163
+ * @param {BaseFetchOptions} [options] Additional options for this fetch
164
+ * @returns {Promise<SoundboardSound|Collection<Snowflake, SoundboardSound>>}
165
+ * @example
166
+ * // Fetch all soundboard sounds from the guild
167
+ * guild.soundboardSounds.fetch()
168
+ * .then(sounds => console.log(`There are ${sounds.size} soundboard sounds.`))
169
+ * .catch(console.error);
170
+ * @example
171
+ * // Fetch a single soundboard sound
172
+ * guild.soundboardSounds.fetch('222078108977594368')
173
+ * .then(sound => console.log(`The soundboard sound name is: ${sound.name}`))
174
+ * .catch(console.error);
175
+ */
176
+ async fetch(id, { cache = true, force = false } = {}) {
177
+ if (id) {
178
+ if (!force) {
179
+ const existing = this.cache.get(id);
180
+ if (existing) return existing;
181
+ }
182
+
183
+ const sound = await this.client.rest.get(Routes.guildSoundboardSound(this.guild.id, id));
184
+ return this._add(sound, cache);
185
+ }
186
+
187
+ const data = await this.client.rest.get(Routes.guildSoundboardSounds(this.guild.id));
188
+ return new Collection(data.map(sound => [sound.sound_id, this._add(sound, cache)]));
189
+ }
190
+ }
191
+
192
+ exports.GuildSoundboardSoundManager = GuildSoundboardSoundManager;
@@ -162,6 +162,18 @@ class ApplicationCommand extends Base {
162
162
  this.contexts ??= null;
163
163
  }
164
164
 
165
+ if ('handler' in data) {
166
+ /**
167
+ * Determines whether the interaction is handled by the app's interactions handler or by Discord.
168
+ * <info>Only available for {@link ApplicationCommandType.PrimaryEntryPoint} commands on
169
+ * applications with the {@link ApplicationFlags.Embedded} flag (i.e, those that have an Activity)</info>
170
+ * @type {?EntryPointCommandHandlerType}
171
+ */
172
+ this.handler = data.handler;
173
+ } else {
174
+ this.handler ??= null;
175
+ }
176
+
165
177
  if ('version' in data) {
166
178
  /**
167
179
  * Autoincrementing version identifier updated during substantial record changes
@@ -204,14 +216,19 @@ class ApplicationCommand extends Base {
204
216
  * @property {string} name The name of the command, must be in all lowercase if type is
205
217
  * {@link ApplicationCommandType.ChatInput}
206
218
  * @property {Object<Locale, string>} [nameLocalizations] The localizations for the command name
207
- * @property {string} description The description of the command, if type is {@link ApplicationCommandType.ChatInput}
219
+ * @property {string} description The description of the command,
220
+ * if type is {@link ApplicationCommandType.ChatInput} or {@link ApplicationCommandType.PrimaryEntryPoint}
208
221
  * @property {boolean} [nsfw] Whether the command is age-restricted
209
222
  * @property {Object<Locale, string>} [descriptionLocalizations] The localizations for the command description,
210
- * if type is {@link ApplicationCommandType.ChatInput}
223
+ * if type is {@link ApplicationCommandType.ChatInput} or {@link ApplicationCommandType.PrimaryEntryPoint}
211
224
  * @property {ApplicationCommandType} [type=ApplicationCommandType.ChatInput] The type of the command
212
225
  * @property {ApplicationCommandOptionData[]} [options] Options for the command
213
226
  * @property {?PermissionResolvable} [defaultMemberPermissions] The bitfield used to determine the default permissions
214
227
  * a member needs in order to run the command
228
+ * @property {ApplicationIntegrationType[]} [integrationTypes] Installation contexts where the command is available
229
+ * @property {InteractionContextType[]} [contexts] Interaction contexts where the command can be used
230
+ * @property {EntryPointCommandHandlerType} [handler] Whether the interaction is handled by the app's
231
+ * interactions handler or by Discord.
215
232
  */
216
233
 
217
234
  /**
@@ -395,7 +412,8 @@ class ApplicationCommand extends Base {
395
412
  this.descriptionLocalizations ?? {},
396
413
  ) ||
397
414
  !isEqual(command.integrationTypes ?? command.integration_types ?? [], this.integrationTypes ?? []) ||
398
- !isEqual(command.contexts ?? [], this.contexts ?? [])
415
+ !isEqual(command.contexts ?? [], this.contexts ?? []) ||
416
+ ('handler' in command && command.handler !== this.handler)
399
417
  ) {
400
418
  return false;
401
419
  }