discord.js 14.18.0 → 14.19.1

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 (45) hide show
  1. package/package.json +7 -6
  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/websocket/handlers/GUILD_SOUNDBOARD_SOUNDS_UPDATE.js +24 -0
  7. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUND_CREATE.js +18 -0
  8. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUND_DELETE.js +5 -0
  9. package/src/client/websocket/handlers/GUILD_SOUNDBOARD_SOUND_UPDATE.js +20 -0
  10. package/src/client/websocket/handlers/SOUNDBOARD_SOUNDS.js +24 -0
  11. package/src/client/websocket/handlers/index.js +5 -0
  12. package/src/errors/ErrorCodes.js +5 -0
  13. package/src/errors/Messages.js +3 -0
  14. package/src/index.js +11 -0
  15. package/src/managers/GuildManager.js +75 -1
  16. package/src/managers/GuildSoundboardSoundManager.js +214 -0
  17. package/src/structures/Component.js +9 -0
  18. package/src/structures/ContainerComponent.js +60 -0
  19. package/src/structures/FileComponent.js +40 -0
  20. package/src/structures/Guild.js +7 -0
  21. package/src/structures/GuildAuditLogsEntry.js +23 -6
  22. package/src/structures/MediaGalleryComponent.js +31 -0
  23. package/src/structures/MediaGalleryItem.js +51 -0
  24. package/src/structures/Message.js +4 -4
  25. package/src/structures/MessageComponentInteraction.js +3 -4
  26. package/src/structures/MessagePayload.js +2 -5
  27. package/src/structures/SectionComponent.js +42 -0
  28. package/src/structures/SeparatorComponent.js +30 -0
  29. package/src/structures/SoundboardSound.js +204 -0
  30. package/src/structures/Sticker.js +1 -1
  31. package/src/structures/TextDisplayComponent.js +20 -0
  32. package/src/structures/ThumbnailComponent.js +49 -0
  33. package/src/structures/UnfurledMediaItem.js +25 -0
  34. package/src/structures/VoiceChannel.js +21 -1
  35. package/src/structures/VoiceChannelEffect.js +9 -0
  36. package/src/structures/Webhook.js +13 -4
  37. package/src/structures/interfaces/TextBasedChannel.js +8 -3
  38. package/src/util/APITypes.js +47 -2
  39. package/src/util/Components.js +130 -19
  40. package/src/util/DataResolver.js +4 -3
  41. package/src/util/Events.js +10 -0
  42. package/src/util/Partials.js +2 -0
  43. package/typings/index.d.mts +319 -52
  44. package/typings/index.d.ts +319 -52
  45. package/typings/index.test-d.ts +73 -9
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.18.0",
4
+ "version": "14.19.1",
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",
@@ -52,17 +52,18 @@
52
52
  "homepage": "https://discord.js.org",
53
53
  "funding": "https://github.com/discordjs/discord.js?sponsor",
54
54
  "dependencies": {
55
- "@discordjs/builders": "^1.10.1",
55
+ "@discordjs/builders": "^1.11.1",
56
56
  "@discordjs/collection": "1.5.3",
57
- "@discordjs/formatters": "^0.6.0",
58
- "@discordjs/ws": "^1.2.1",
57
+ "@discordjs/formatters": "^0.6.1",
58
+ "@discordjs/ws": "^1.2.2",
59
59
  "@sapphire/snowflake": "3.5.3",
60
- "discord-api-types": "^0.37.119",
60
+ "discord-api-types": "^0.38.1",
61
61
  "fast-deep-equal": "3.1.3",
62
62
  "lodash.snakecase": "4.1.1",
63
+ "magic-bytes.js": "^1.10.0",
63
64
  "tslib": "^2.6.3",
64
65
  "undici": "6.21.1",
65
- "@discordjs/rest": "^2.4.3",
66
+ "@discordjs/rest": "^2.5.0",
66
67
  "@discordjs/util": "^1.1.1"
67
68
  },
68
69
  "devDependencies": {
@@ -18,6 +18,7 @@ const ClientPresence = require('../structures/ClientPresence');
18
18
  const GuildPreview = require('../structures/GuildPreview');
19
19
  const GuildTemplate = require('../structures/GuildTemplate');
20
20
  const Invite = require('../structures/Invite');
21
+ const { SoundboardSound } = require('../structures/SoundboardSound');
21
22
  const { Sticker } = require('../structures/Sticker');
22
23
  const StickerPack = require('../structures/StickerPack');
23
24
  const VoiceRegion = require('../structures/VoiceRegion');
@@ -390,6 +391,19 @@ class Client extends BaseClient {
390
391
  return this.fetchStickerPacks();
391
392
  }
392
393
 
394
+ /**
395
+ * Obtains the list of default soundboard sounds.
396
+ * @returns {Promise<Collection<string, SoundboardSound>>}
397
+ * @example
398
+ * client.fetchDefaultSoundboardSounds()
399
+ * .then(sounds => console.log(`Available soundboard sounds are: ${sounds.map(sound => sound.name).join(', ')}`))
400
+ * .catch(console.error);
401
+ */
402
+ async fetchDefaultSoundboardSounds() {
403
+ const data = await this.rest.get(Routes.soundboardDefaultSounds());
404
+ return new Collection(data.map(sound => [sound.sound_id, new SoundboardSound(this, sound)]));
405
+ }
406
+
393
407
  /**
394
408
  * Obtains a guild preview from Discord, available for all guilds the bot is in and all Discoverable guilds.
395
409
  * @param {GuildResolvable} guild The guild to fetch the preview for
@@ -112,6 +112,10 @@ class GenericAction {
112
112
  return this.getPayload({ user_id: id }, manager, id, Partials.ThreadMember, false);
113
113
  }
114
114
 
115
+ getSoundboardSound(data, guild) {
116
+ return this.getPayload(data, guild.soundboardSounds, data.sound_id, Partials.SoundboardSound);
117
+ }
118
+
115
119
  spreadInjectedData(data) {
116
120
  return Object.fromEntries(Object.getOwnPropertySymbols(data).map(symbol => [symbol, data[symbol]]));
117
121
  }
@@ -43,6 +43,7 @@ class ActionsManager {
43
43
  this.register(require('./GuildScheduledEventUpdate'));
44
44
  this.register(require('./GuildScheduledEventUserAdd'));
45
45
  this.register(require('./GuildScheduledEventUserRemove'));
46
+ this.register(require('./GuildSoundboardSoundDelete.js'));
46
47
  this.register(require('./GuildStickerCreate'));
47
48
  this.register(require('./GuildStickerDelete'));
48
49
  this.register(require('./GuildStickerUpdate'));
@@ -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
+ module.exports = GuildSoundboardSoundDeleteAction;
@@ -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 handlers = Object.fromEntries([
32
32
  ['GUILD_SCHEDULED_EVENT_UPDATE', require('./GUILD_SCHEDULED_EVENT_UPDATE')],
33
33
  ['GUILD_SCHEDULED_EVENT_USER_ADD', require('./GUILD_SCHEDULED_EVENT_USER_ADD')],
34
34
  ['GUILD_SCHEDULED_EVENT_USER_REMOVE', require('./GUILD_SCHEDULED_EVENT_USER_REMOVE')],
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')],
36
40
  ['GUILD_UPDATE', require('./GUILD_UPDATE')],
37
41
  ['INTERACTION_CREATE', require('./INTERACTION_CREATE')],
@@ -50,6 +54,7 @@ const handlers = Object.fromEntries([
50
54
  ['PRESENCE_UPDATE', require('./PRESENCE_UPDATE')],
51
55
  ['READY', require('./READY')],
52
56
  ['RESUMED', require('./RESUMED')],
57
+ ['SOUNDBOARD_SOUNDS', require('./SOUNDBOARD_SOUNDS.js')],
53
58
  ['STAGE_INSTANCE_CREATE', require('./STAGE_INSTANCE_CREATE')],
54
59
  ['STAGE_INSTANCE_DELETE', require('./STAGE_INSTANCE_DELETE')],
55
60
  ['STAGE_INSTANCE_UPDATE', require('./STAGE_INSTANCE_UPDATE')],
@@ -106,6 +106,7 @@
106
106
  * @property {'GuildChannelUnowned'} GuildChannelUnowned
107
107
  * @property {'GuildOwned'} GuildOwned
108
108
  * @property {'GuildMembersTimeout'} GuildMembersTimeout
109
+ * @property {'GuildSoundboardSoundsTimeout'} GuildSoundboardSoundsTimeout
109
110
  * @property {'GuildUncachedMe'} GuildUncachedMe
110
111
  * @property {'ChannelNotCached'} ChannelNotCached
111
112
  * @property {'StageChannelResolve'} StageChannelResolve
@@ -131,6 +132,8 @@
131
132
  * @property {'MissingManageEmojisAndStickersPermission'} MissingManageEmojisAndStickersPermission
132
133
  * <warn>This property is deprecated. Use `MissingManageGuildExpressionsPermission` instead.</warn>
133
134
  *
135
+
136
+ * @property {'NotGuildSoundboardSound'} NotGuildSoundboardSound
134
137
  * @property {'NotGuildSticker'} NotGuildSticker
135
138
 
136
139
  * @property {'ReactionResolveUser'} ReactionResolveUser
@@ -266,6 +269,7 @@ const keys = [
266
269
  'GuildChannelUnowned',
267
270
  'GuildOwned',
268
271
  'GuildMembersTimeout',
272
+ 'GuildSoundboardSoundsTimeout',
269
273
  'GuildUncachedMe',
270
274
  'ChannelNotCached',
271
275
  'StageChannelResolve',
@@ -290,6 +294,7 @@ const keys = [
290
294
  'MissingManageGuildExpressionsPermission',
291
295
  'MissingManageEmojisAndStickersPermission',
292
296
 
297
+ 'NotGuildSoundboardSound',
293
298
  'NotGuildSticker',
294
299
 
295
300
  'ReactionResolveUser',
@@ -91,6 +91,7 @@ const Messages = {
91
91
  [DjsErrorCodes.GuildChannelUnowned]: "The fetched channel does not belong to this manager's guild.",
92
92
  [DjsErrorCodes.GuildOwned]: 'Guild is owned by the client.',
93
93
  [DjsErrorCodes.GuildMembersTimeout]: "Members didn't arrive in time.",
94
+ [DjsErrorCodes.GuildSoundboardSoundsTimeout]: "Soundboard sounds didn't arrive in time.",
94
95
  [DjsErrorCodes.GuildUncachedMe]: 'The client user as a member of this guild is uncached.',
95
96
  [DjsErrorCodes.ChannelNotCached]: 'Could not find the channel where this message came from in the cache!',
96
97
  [DjsErrorCodes.StageChannelResolve]: 'Could not resolve channel to a stage channel.',
@@ -118,6 +119,8 @@ const Messages = {
118
119
  [DjsErrorCodes.MissingManageEmojisAndStickersPermission]: guild =>
119
120
  `Client must have Manage Emojis and Stickers permission in guild ${guild} to see emoji authors.`,
120
121
 
122
+ [DjsErrorCodes.NotGuildSoundboardSound]: action =>
123
+ `Soundboard sound is a default (non-guild) soundboard sound and can't be ${action}.`,
121
124
  [DjsErrorCodes.NotGuildSticker]: 'Sticker is a standard (non-guild) sticker and has no author.',
122
125
 
123
126
  [DjsErrorCodes.ReactionResolveUser]: "Couldn't resolve the user id to remove from the reaction.",
package/src/index.js CHANGED
@@ -75,6 +75,7 @@ exports.GuildMemberManager = require('./managers/GuildMemberManager');
75
75
  exports.GuildMemberRoleManager = require('./managers/GuildMemberRoleManager');
76
76
  exports.GuildMessageManager = require('./managers/GuildMessageManager');
77
77
  exports.GuildScheduledEventManager = require('./managers/GuildScheduledEventManager');
78
+ exports.GuildSoundboardSoundManager = require('./managers/GuildSoundboardSoundManager.js').GuildSoundboardSoundManager;
78
79
  exports.GuildStickerManager = require('./managers/GuildStickerManager');
79
80
  exports.GuildTextThreadManager = require('./managers/GuildTextThreadManager');
80
81
  exports.MessageManager = require('./managers/MessageManager');
@@ -123,12 +124,14 @@ exports.CommandInteraction = require('./structures/CommandInteraction');
123
124
  exports.Collector = require('./structures/interfaces/Collector');
124
125
  exports.CommandInteractionOptionResolver = require('./structures/CommandInteractionOptionResolver');
125
126
  exports.Component = require('./structures/Component');
127
+ exports.ContainerComponent = require('./structures/ContainerComponent');
126
128
  exports.ContextMenuCommandInteraction = require('./structures/ContextMenuCommandInteraction');
127
129
  exports.DMChannel = require('./structures/DMChannel');
128
130
  exports.Embed = require('./structures/Embed');
129
131
  exports.EmbedBuilder = require('./structures/EmbedBuilder');
130
132
  exports.Emoji = require('./structures/Emoji').Emoji;
131
133
  exports.Entitlement = require('./structures/Entitlement').Entitlement;
134
+ exports.FileComponent = require('./structures/FileComponent');
132
135
  exports.ForumChannel = require('./structures/ForumChannel');
133
136
  exports.Guild = require('./structures/Guild').Guild;
134
137
  exports.GuildAuditLogs = require('./structures/GuildAuditLogs');
@@ -161,6 +164,8 @@ exports.Attachment = require('./structures/Attachment');
161
164
  exports.AttachmentBuilder = require('./structures/AttachmentBuilder');
162
165
  exports.ModalBuilder = require('./structures/ModalBuilder');
163
166
  exports.MediaChannel = require('./structures/MediaChannel');
167
+ exports.MediaGalleryComponent = require('./structures/MediaGalleryComponent');
168
+ exports.MediaGalleryItem = require('./structures/MediaGalleryItem');
164
169
  exports.MessageCollector = require('./structures/MessageCollector');
165
170
  exports.MessageComponentInteraction = require('./structures/MessageComponentInteraction');
166
171
  exports.MessageContextMenuCommandInteraction = require('./structures/MessageContextMenuCommandInteraction');
@@ -180,6 +185,7 @@ exports.ReactionCollector = require('./structures/ReactionCollector');
180
185
  exports.ReactionEmoji = require('./structures/ReactionEmoji');
181
186
  exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets;
182
187
  exports.Role = require('./structures/Role').Role;
188
+ exports.SectionComponent = require('./structures/SectionComponent');
183
189
  exports.SelectMenuBuilder = require('./structures/SelectMenuBuilder');
184
190
  exports.ChannelSelectMenuBuilder = require('./structures/ChannelSelectMenuBuilder');
185
191
  exports.MentionableSelectMenuBuilder = require('./structures/MentionableSelectMenuBuilder');
@@ -201,7 +207,9 @@ exports.RoleSelectMenuInteraction = require('./structures/RoleSelectMenuInteract
201
207
  exports.StringSelectMenuInteraction = require('./structures/StringSelectMenuInteraction');
202
208
  exports.UserSelectMenuInteraction = require('./structures/UserSelectMenuInteraction');
203
209
  exports.SelectMenuOptionBuilder = require('./structures/SelectMenuOptionBuilder');
210
+ exports.SeparatorComponent = require('./structures/SeparatorComponent');
204
211
  exports.SKU = require('./structures/SKU').SKU;
212
+ exports.SoundboardSound = require('./structures/SoundboardSound.js').SoundboardSound;
205
213
  exports.StringSelectMenuOptionBuilder = require('./structures/StringSelectMenuOptionBuilder');
206
214
  exports.StageChannel = require('./structures/StageChannel');
207
215
  exports.StageInstance = require('./structures/StageInstance').StageInstance;
@@ -211,12 +219,15 @@ exports.StickerPack = require('./structures/StickerPack');
211
219
  exports.Team = require('./structures/Team');
212
220
  exports.TeamMember = require('./structures/TeamMember');
213
221
  exports.TextChannel = require('./structures/TextChannel');
222
+ exports.TextDisplayComponent = require('./structures/TextDisplayComponent');
214
223
  exports.TextInputBuilder = require('./structures/TextInputBuilder');
215
224
  exports.TextInputComponent = require('./structures/TextInputComponent');
216
225
  exports.ThreadChannel = require('./structures/ThreadChannel');
217
226
  exports.ThreadMember = require('./structures/ThreadMember');
218
227
  exports.ThreadOnlyChannel = require('./structures/ThreadOnlyChannel');
228
+ exports.ThumbnailComponent = require('./structures/ThumbnailComponent');
219
229
  exports.Typing = require('./structures/Typing');
230
+ exports.UnfurledMediaItem = require('./structures/UnfurledMediaItem');
220
231
  exports.User = require('./structures/User');
221
232
  exports.UserContextMenuCommandInteraction = require('./structures/UserContextMenuCommandInteraction');
222
233
  exports.VoiceChannelEffect = require('./structures/VoiceChannelEffect');
@@ -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');
9
+ const { ErrorCodes, DiscordjsError } = require('../errors/index.js');
9
10
  const ShardClientUtil = require('../sharding/ShardClientUtil');
10
11
  const { Guild } = require('../structures/Guild');
11
12
  const GuildChannel = require('../structures/GuildChannel');
@@ -282,6 +283,79 @@ 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 = this.client.options.shardCount;
306
+ const shardIds = new Map();
307
+
308
+ for (const guildId of guildIds) {
309
+ const shardId = ShardClientUtil.shardIdForGuildId(guildId, shardCount);
310
+ const group = shardIds.get(shardId);
311
+
312
+ if (group) group.push(guildId);
313
+ else shardIds.set(shardId, [guildId]);
314
+ }
315
+
316
+ for (const [shardId, shardGuildIds] of shardIds) {
317
+ this.client.ws.shards.get(shardId).send({
318
+ op: GatewayOpcodes.RequestSoundboardSounds,
319
+ d: {
320
+ guild_ids: shardGuildIds,
321
+ },
322
+ });
323
+ }
324
+
325
+ return new Promise((resolve, reject) => {
326
+ const remainingGuildIds = new Set(guildIds);
327
+
328
+ const fetchedSoundboardSounds = new Collection();
329
+
330
+ const handler = (soundboardSounds, guild) => {
331
+ timeout.refresh();
332
+
333
+ if (!remainingGuildIds.has(guild.id)) return;
334
+
335
+ fetchedSoundboardSounds.set(guild.id, soundboardSounds);
336
+
337
+ remainingGuildIds.delete(guild.id);
338
+
339
+ if (remainingGuildIds.size === 0) {
340
+ clearTimeout(timeout);
341
+ this.client.removeListener(Events.SoundboardSounds, handler);
342
+ this.client.decrementMaxListeners();
343
+
344
+ resolve(fetchedSoundboardSounds);
345
+ }
346
+ };
347
+
348
+ const timeout = setTimeout(() => {
349
+ this.client.removeListener(Events.SoundboardSounds, handler);
350
+ this.client.decrementMaxListeners();
351
+ reject(new DiscordjsError(ErrorCodes.GuildSoundboardSoundsTimeout));
352
+ }, time).unref();
353
+
354
+ this.client.incrementMaxListeners();
355
+ this.client.on(Events.SoundboardSounds, handler);
356
+ });
357
+ }
358
+
285
359
  /**
286
360
  * Options used to set incident actions. Supplying `null` to any option will disable the action.
287
361
  * @typedef {Object} IncidentActionsEditOptions
@@ -0,0 +1,214 @@
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
+ * Options used to fetch a soundboard sound.
162
+ * @typedef {BaseFetchOptions} FetchSoundboardSoundOptions
163
+ * @property {SoundboardSoundResolvable} soundboardSound The soundboard sound to fetch
164
+ */
165
+
166
+ /**
167
+ * Options used to fetch soundboard sounds from Discord
168
+ * @typedef {Object} FetchGuildSoundboardSoundsOptions
169
+ * @property {boolean} [cache] Whether to cache the fetched soundboard sounds
170
+ */
171
+
172
+ /* eslint-disable max-len */
173
+ /**
174
+ * Obtains one or more soundboard sounds from Discord, or the soundboard sound cache if they're already available.
175
+ * @param {SoundboardSoundResolvable|FetchSoundboardSoundOptions|FetchGuildSoundboardSoundsOptions} [options] Options for fetching soundboard sound(s)
176
+ * @returns {Promise<SoundboardSound|Collection<Snowflake, SoundboardSound>>}
177
+ * @example
178
+ * // Fetch a single soundboard sound
179
+ * guild.soundboardSounds.fetch('222078108977594368')
180
+ * .then(sound => console.log(`The soundboard sound name is: ${sound.name}`))
181
+ * .catch(console.error);
182
+ * @example
183
+ * // Fetch all soundboard sounds from the guild
184
+ * guild.soundboardSounds.fetch()
185
+ * .then(sounds => console.log(`There are ${sounds.size} soundboard sounds.`))
186
+ * .catch(console.error);
187
+ */
188
+ /* eslint-enable max-len */
189
+ async fetch(options) {
190
+ if (!options) return this._fetchMany();
191
+ const { cache, force, soundboardSound } = options;
192
+ const resolvedSoundboardSound = this.resolveId(soundboardSound ?? options);
193
+ if (resolvedSoundboardSound) return this._fetchSingle({ cache, force, soundboardSound });
194
+ return this._fetchMany({ cache });
195
+ }
196
+
197
+ async _fetchSingle({ cache, force, soundboardSound } = {}) {
198
+ if (!force) {
199
+ const existing = this.cache.get(soundboardSound);
200
+ if (existing) return existing;
201
+ }
202
+
203
+ const data = await this.client.rest.get(Routes.guildSoundboardSound(this.guild.id, soundboardSound));
204
+ return this._add(data, cache);
205
+ }
206
+
207
+ async _fetchMany({ cache } = {}) {
208
+ const data = await this.client.rest.get(Routes.guildSoundboardSounds(this.guild.id));
209
+
210
+ return data.items.reduce((coll, sound) => coll.set(sound.sound_id, this._add(sound, cache)), new Collection());
211
+ }
212
+ }
213
+
214
+ exports.GuildSoundboardSoundManager = GuildSoundboardSoundManager;